Skip to content

Commit faa1b57

Browse files
authored
Global TaskPool API improvements (#10008)
# Objective Reduce code duplication and improve APIs of Bevy's [global taskpools](https://github.com/bevyengine/bevy/blob/main/crates/bevy_tasks/src/usages.rs). ## Solution - As all three of the global taskpools have identical implementations and only differ in their identifiers, this PR moves the implementation into a macro to reduce code duplication. - The `init` method is renamed to `get_or_init` to more accurately reflect what it really does. - Add a new `try_get` method that just returns `None` when the pool is uninitialized, to complement the other getter methods. - Minor documentation improvements to accompany the above changes. --- ## Changelog - Added a new `try_get` method to the global TaskPools - The global TaskPools' `init` method has been renamed to `get_or_init` for clarity - Documentation improvements ## Migration Guide - Uses of `ComputeTaskPool::init`, `AsyncComputeTaskPool::init` and `IoTaskPool::init` should be changed to `::get_or_init`.
1 parent 7d504b8 commit faa1b57

File tree

7 files changed

+76
-106
lines changed

7 files changed

+76
-106
lines changed

benches/benches/bevy_ecs/iteration/heavy_compute.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub fn heavy_compute(c: &mut Criterion) {
2020
group.warm_up_time(std::time::Duration::from_millis(500));
2121
group.measurement_time(std::time::Duration::from_secs(4));
2222
group.bench_function("base", |b| {
23-
ComputeTaskPool::init(TaskPool::default);
23+
ComputeTaskPool::get_or_init(TaskPool::default);
2424

2525
let mut world = World::default();
2626

crates/bevy_core/src/task_pool_options.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ impl TaskPoolOptions {
107107
trace!("IO Threads: {}", io_threads);
108108
remaining_threads = remaining_threads.saturating_sub(io_threads);
109109

110-
IoTaskPool::init(|| {
110+
IoTaskPool::get_or_init(|| {
111111
TaskPoolBuilder::default()
112112
.num_threads(io_threads)
113113
.thread_name("IO Task Pool".to_string())
@@ -124,7 +124,7 @@ impl TaskPoolOptions {
124124
trace!("Async Compute Threads: {}", async_compute_threads);
125125
remaining_threads = remaining_threads.saturating_sub(async_compute_threads);
126126

127-
AsyncComputeTaskPool::init(|| {
127+
AsyncComputeTaskPool::get_or_init(|| {
128128
TaskPoolBuilder::default()
129129
.num_threads(async_compute_threads)
130130
.thread_name("Async Compute Task Pool".to_string())
@@ -141,7 +141,7 @@ impl TaskPoolOptions {
141141

142142
trace!("Compute Threads: {}", compute_threads);
143143

144-
ComputeTaskPool::init(|| {
144+
ComputeTaskPool::get_or_init(|| {
145145
TaskPoolBuilder::default()
146146
.num_threads(compute_threads)
147147
.thread_name("Compute Task Pool".to_string())

crates/bevy_ecs/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ mod tests {
400400

401401
#[test]
402402
fn par_for_each_dense() {
403-
ComputeTaskPool::init(TaskPool::default);
403+
ComputeTaskPool::get_or_init(TaskPool::default);
404404
let mut world = World::new();
405405
let e1 = world.spawn(A(1)).id();
406406
let e2 = world.spawn(A(2)).id();
@@ -423,7 +423,7 @@ mod tests {
423423

424424
#[test]
425425
fn par_for_each_sparse() {
426-
ComputeTaskPool::init(TaskPool::default);
426+
ComputeTaskPool::get_or_init(TaskPool::default);
427427
let mut world = World::new();
428428
let e1 = world.spawn(SparseStored(1)).id();
429429
let e2 = world.spawn(SparseStored(2)).id();

crates/bevy_ecs/src/schedule/executor/multi_threaded.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ impl SystemExecutor for MultiThreadedExecutor {
195195
mut conditions,
196196
} = SyncUnsafeSchedule::new(schedule);
197197

198-
ComputeTaskPool::init(TaskPool::default).scope_with_executor(
198+
ComputeTaskPool::get_or_init(TaskPool::default).scope_with_executor(
199199
false,
200200
thread_executor,
201201
|scope| {

crates/bevy_ecs/src/schedule/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ mod tests {
104104

105105
let mut world = World::default();
106106
let mut schedule = Schedule::default();
107-
let thread_count = ComputeTaskPool::init(TaskPool::default).thread_num();
107+
let thread_count = ComputeTaskPool::get_or_init(TaskPool::default).thread_num();
108108

109109
let barrier = Arc::new(Barrier::new(thread_count));
110110

crates/bevy_tasks/src/usages.rs

Lines changed: 63 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,77 @@
11
use super::TaskPool;
22
use std::{ops::Deref, sync::OnceLock};
33

4-
static COMPUTE_TASK_POOL: OnceLock<ComputeTaskPool> = OnceLock::new();
5-
static ASYNC_COMPUTE_TASK_POOL: OnceLock<AsyncComputeTaskPool> = OnceLock::new();
6-
static IO_TASK_POOL: OnceLock<IoTaskPool> = OnceLock::new();
7-
8-
/// A newtype for a task pool for CPU-intensive work that must be completed to
9-
/// deliver the next frame
10-
///
11-
/// See [`TaskPool`] documentation for details on Bevy tasks.
12-
/// [`AsyncComputeTaskPool`] should be preferred if the work does not have to be
13-
/// completed before the next frame.
14-
#[derive(Debug)]
15-
pub struct ComputeTaskPool(TaskPool);
16-
17-
impl ComputeTaskPool {
18-
/// Initializes the global [`ComputeTaskPool`] instance.
19-
pub fn init(f: impl FnOnce() -> TaskPool) -> &'static Self {
20-
COMPUTE_TASK_POOL.get_or_init(|| Self(f()))
21-
}
22-
23-
/// Gets the global [`ComputeTaskPool`] instance.
24-
///
25-
/// # Panics
26-
/// Panics if no pool has been initialized yet.
27-
pub fn get() -> &'static Self {
28-
COMPUTE_TASK_POOL.get().expect(
29-
"A ComputeTaskPool has not been initialized yet. Please call \
30-
ComputeTaskPool::init beforehand.",
31-
)
32-
}
4+
macro_rules! taskpool {
5+
($(#[$attr:meta])* ($static:ident, $type:ident)) => {
6+
static $static: OnceLock<$type> = OnceLock::new();
7+
8+
$(#[$attr])*
9+
#[derive(Debug)]
10+
pub struct $type(TaskPool);
11+
12+
impl $type {
13+
#[doc = concat!(" Gets the global [`", stringify!($type), "`] instance, or initializes it with `f`.")]
14+
pub fn get_or_init(f: impl FnOnce() -> TaskPool) -> &'static Self {
15+
$static.get_or_init(|| Self(f()))
16+
}
17+
18+
#[doc = concat!(" Attempts to get the global [`", stringify!($type), "`] instance, \
19+
or returns `None` if it is not initialized.")]
20+
pub fn try_get() -> Option<&'static Self> {
21+
$static.get()
22+
}
23+
24+
#[doc = concat!(" Gets the global [`", stringify!($type), "`] instance.")]
25+
#[doc = ""]
26+
#[doc = " # Panics"]
27+
#[doc = " Panics if the global instance has not been initialized yet."]
28+
pub fn get() -> &'static Self {
29+
$static.get().expect(
30+
concat!(
31+
"The ",
32+
stringify!($type),
33+
" has not been initialized yet. Please call ",
34+
stringify!($type),
35+
"::get_or_init beforehand."
36+
)
37+
)
38+
}
39+
}
40+
41+
impl Deref for $type {
42+
type Target = TaskPool;
43+
44+
fn deref(&self) -> &Self::Target {
45+
&self.0
46+
}
47+
}
48+
};
3349
}
3450

35-
impl Deref for ComputeTaskPool {
36-
type Target = TaskPool;
37-
38-
fn deref(&self) -> &Self::Target {
39-
&self.0
40-
}
41-
}
42-
43-
/// A newtype for a task pool for CPU-intensive work that may span across multiple frames
44-
///
45-
/// See [`TaskPool`] documentation for details on Bevy tasks. Use [`ComputeTaskPool`] if
46-
/// the work must be complete before advancing to the next frame.
47-
#[derive(Debug)]
48-
pub struct AsyncComputeTaskPool(TaskPool);
49-
50-
impl AsyncComputeTaskPool {
51-
/// Initializes the global [`AsyncComputeTaskPool`] instance.
52-
pub fn init(f: impl FnOnce() -> TaskPool) -> &'static Self {
53-
ASYNC_COMPUTE_TASK_POOL.get_or_init(|| Self(f()))
54-
}
55-
56-
/// Gets the global [`AsyncComputeTaskPool`] instance.
51+
taskpool! {
52+
/// A newtype for a task pool for CPU-intensive work that must be completed to
53+
/// deliver the next frame
5754
///
58-
/// # Panics
59-
/// Panics if no pool has been initialized yet.
60-
pub fn get() -> &'static Self {
61-
ASYNC_COMPUTE_TASK_POOL.get().expect(
62-
"A AsyncComputeTaskPool has not been initialized yet. Please call \
63-
AsyncComputeTaskPool::init beforehand.",
64-
)
65-
}
66-
}
67-
68-
impl Deref for AsyncComputeTaskPool {
69-
type Target = TaskPool;
70-
71-
fn deref(&self) -> &Self::Target {
72-
&self.0
73-
}
55+
/// See [`TaskPool`] documentation for details on Bevy tasks.
56+
/// [`AsyncComputeTaskPool`] should be preferred if the work does not have to be
57+
/// completed before the next frame.
58+
(COMPUTE_TASK_POOL, ComputeTaskPool)
7459
}
7560

76-
/// A newtype for a task pool for IO-intensive work (i.e. tasks that spend very little time in a
77-
/// "woken" state)
78-
#[derive(Debug)]
79-
pub struct IoTaskPool(TaskPool);
80-
81-
impl IoTaskPool {
82-
/// Initializes the global [`IoTaskPool`] instance.
83-
pub fn init(f: impl FnOnce() -> TaskPool) -> &'static Self {
84-
IO_TASK_POOL.get_or_init(|| Self(f()))
85-
}
86-
87-
/// Gets the global [`IoTaskPool`] instance.
61+
taskpool! {
62+
/// A newtype for a task pool for CPU-intensive work that may span across multiple frames
8863
///
89-
/// # Panics
90-
/// Panics if no pool has been initialized yet.
91-
pub fn get() -> &'static Self {
92-
IO_TASK_POOL.get().expect(
93-
"A IoTaskPool has not been initialized yet. Please call \
94-
IoTaskPool::init beforehand.",
95-
)
96-
}
64+
/// See [`TaskPool`] documentation for details on Bevy tasks.
65+
/// Use [`ComputeTaskPool`] if the work must be complete before advancing to the next frame.
66+
(ASYNC_COMPUTE_TASK_POOL, AsyncComputeTaskPool)
9767
}
9868

99-
impl Deref for IoTaskPool {
100-
type Target = TaskPool;
101-
102-
fn deref(&self) -> &Self::Target {
103-
&self.0
104-
}
69+
taskpool! {
70+
/// A newtype for a task pool for IO-intensive work (i.e. tasks that spend very little time in a
71+
/// "woken" state)
72+
///
73+
/// See [`TaskPool`] documentation for details on Bevy tasks.
74+
(IO_TASK_POOL, IoTaskPool)
10575
}
10676

10777
/// A function used by `bevy_core` to tick the global tasks pools on the main thread.

crates/bevy_transform/src/systems.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ mod test {
193193

194194
#[test]
195195
fn correct_parent_removed() {
196-
ComputeTaskPool::init(TaskPool::default);
196+
ComputeTaskPool::get_or_init(TaskPool::default);
197197
let mut world = World::default();
198198
let offset_global_transform =
199199
|offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
@@ -248,7 +248,7 @@ mod test {
248248

249249
#[test]
250250
fn did_propagate() {
251-
ComputeTaskPool::init(TaskPool::default);
251+
ComputeTaskPool::get_or_init(TaskPool::default);
252252
let mut world = World::default();
253253

254254
let mut schedule = Schedule::default();
@@ -326,7 +326,7 @@ mod test {
326326

327327
#[test]
328328
fn correct_children() {
329-
ComputeTaskPool::init(TaskPool::default);
329+
ComputeTaskPool::get_or_init(TaskPool::default);
330330
let mut world = World::default();
331331

332332
let mut schedule = Schedule::default();
@@ -404,7 +404,7 @@ mod test {
404404
#[test]
405405
fn correct_transforms_when_no_children() {
406406
let mut app = App::new();
407-
ComputeTaskPool::init(TaskPool::default);
407+
ComputeTaskPool::get_or_init(TaskPool::default);
408408

409409
app.add_systems(Update, (sync_simple_transforms, propagate_transforms));
410410

@@ -446,7 +446,7 @@ mod test {
446446
#[test]
447447
#[should_panic]
448448
fn panic_when_hierarchy_cycle() {
449-
ComputeTaskPool::init(TaskPool::default);
449+
ComputeTaskPool::get_or_init(TaskPool::default);
450450
// We cannot directly edit Parent and Children, so we use a temp world to break
451451
// the hierarchy's invariants.
452452
let mut temp = World::new();

0 commit comments

Comments
 (0)