Skip to content

Commit 012ae07

Browse files
committed
Add global init and get accessors for all newtyped TaskPools (#2250)
Right now, a direct reference to the target TaskPool is required to launch tasks on the pools, despite the three newtyped pools (AsyncComputeTaskPool, ComputeTaskPool, and IoTaskPool) effectively acting as global instances. The need to pass a TaskPool reference adds notable friction to spawning subtasks within existing tasks. Possible use cases for this may include chaining tasks within the same pool like spawning separate send/receive I/O tasks after waiting on a network connection to be established, or allowing cross-pool dependent tasks like starting dependent multi-frame computations following a long I/O load. Other task execution runtimes provide static access to spawning tasks (i.e. `tokio::spawn`), which is notably easier to use than the reference passing required by `bevy_tasks` right now. This PR makes does the following: * Adds `*TaskPool::init` which initializes a `OnceCell`'ed with a provided TaskPool. Failing if the pool has already been initialized. * Adds `*TaskPool::get` which fetches the initialized global pool of the respective type or panics. This generally should not be an issue in normal Bevy use, as the pools are initialized before they are accessed. * Updated default task pool initialization to either pull the global handles and save them as resources, or if they are already initialized, pull the a cloned global handle as the resource. This should make it notably easier to build more complex task hierarchies for dependent tasks. It should also make writing bevy-adjacent, but not strictly bevy-only plugin crates easier, as the global pools ensure it's all running on the same threads. One alternative considered is keeping a thread-local reference to the pool for all threads in each pool to enable the same `tokio::spawn` interface. This would spawn tasks on the same pool that a task is currently running in. However this potentially leads to potential footgun situations where long running blocking tasks run on `ComputeTaskPool`.
1 parent 5ace79f commit 012ae07

File tree

19 files changed

+226
-219
lines changed

19 files changed

+226
-219
lines changed

benches/benches/bevy_ecs/ecs_bench_suite/heavy_compute.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub struct Benchmark(World, Box<dyn System<In = (), Out = ()>>);
1818

1919
impl Benchmark {
2020
pub fn new() -> Self {
21+
ComputeTaskPool::init(TaskPool::default);
22+
2123
let mut world = World::default();
2224

2325
world.spawn_batch((0..1000).map(|_| {
@@ -39,7 +41,6 @@ impl Benchmark {
3941
});
4042
}
4143

42-
world.insert_resource(ComputeTaskPool(TaskPool::default()));
4344
let mut system = IntoSystem::into_system(sys);
4445
system.initialize(&mut world);
4546
system.update_archetype_component_access(&world);

crates/bevy_app/src/app.rs

+1-11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use bevy_ecs::{
1010
system::Resource,
1111
world::World,
1212
};
13-
use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool};
1413
use bevy_utils::{tracing::debug, HashMap};
1514
use std::fmt::Debug;
1615

@@ -863,18 +862,9 @@ impl App {
863862
pub fn add_sub_app(
864863
&mut self,
865864
label: impl AppLabel,
866-
mut app: App,
865+
app: App,
867866
sub_app_runner: impl Fn(&mut World, &mut App) + 'static,
868867
) -> &mut Self {
869-
if let Some(pool) = self.world.get_resource::<ComputeTaskPool>() {
870-
app.world.insert_resource(pool.clone());
871-
}
872-
if let Some(pool) = self.world.get_resource::<AsyncComputeTaskPool>() {
873-
app.world.insert_resource(pool.clone());
874-
}
875-
if let Some(pool) = self.world.get_resource::<IoTaskPool>() {
876-
app.world.insert_resource(pool.clone());
877-
}
878868
self.sub_apps.insert(
879869
Box::new(label),
880870
SubApp {

crates/bevy_asset/src/asset_server.rs

+7-11
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
use anyhow::Result;
88
use bevy_ecs::system::{Res, ResMut};
99
use bevy_log::warn;
10-
use bevy_tasks::TaskPool;
10+
use bevy_tasks::IoTaskPool;
1111
use bevy_utils::{Entry, HashMap, Uuid};
1212
use crossbeam_channel::TryRecvError;
1313
use parking_lot::{Mutex, RwLock};
@@ -56,7 +56,6 @@ pub struct AssetServerInternal {
5656
loaders: RwLock<Vec<Arc<dyn AssetLoader>>>,
5757
extension_to_loader_index: RwLock<HashMap<String, usize>>,
5858
handle_to_path: Arc<RwLock<HashMap<HandleId, AssetPath<'static>>>>,
59-
task_pool: TaskPool,
6059
}
6160

6261
/// Loads assets from the filesystem on background threads
@@ -66,11 +65,11 @@ pub struct AssetServer {
6665
}
6766

6867
impl AssetServer {
69-
pub fn new<T: AssetIo>(source_io: T, task_pool: TaskPool) -> Self {
70-
Self::with_boxed_io(Box::new(source_io), task_pool)
68+
pub fn new<T: AssetIo>(source_io: T) -> Self {
69+
Self::with_boxed_io(Box::new(source_io))
7170
}
7271

73-
pub fn with_boxed_io(asset_io: Box<dyn AssetIo>, task_pool: TaskPool) -> Self {
72+
pub fn with_boxed_io(asset_io: Box<dyn AssetIo>) -> Self {
7473
AssetServer {
7574
server: Arc::new(AssetServerInternal {
7675
loaders: Default::default(),
@@ -79,7 +78,6 @@ impl AssetServer {
7978
asset_ref_counter: Default::default(),
8079
handle_to_path: Default::default(),
8180
asset_lifecycles: Default::default(),
82-
task_pool,
8381
asset_io,
8482
}),
8583
}
@@ -315,7 +313,6 @@ impl AssetServer {
315313
&self.server.asset_ref_counter.channel,
316314
self.asset_io(),
317315
version,
318-
&self.server.task_pool,
319316
);
320317

321318
if let Err(err) = asset_loader
@@ -377,8 +374,7 @@ impl AssetServer {
377374
pub(crate) fn load_untracked(&self, asset_path: AssetPath<'_>, force: bool) -> HandleId {
378375
let server = self.clone();
379376
let owned_path = asset_path.to_owned();
380-
self.server
381-
.task_pool
377+
IoTaskPool::get()
382378
.spawn(async move {
383379
if let Err(err) = server.load_async(owned_path, force).await {
384380
warn!("{}", err);
@@ -620,8 +616,8 @@ mod test {
620616

621617
fn setup(asset_path: impl AsRef<Path>) -> AssetServer {
622618
use crate::FileAssetIo;
623-
624-
AssetServer::new(FileAssetIo::new(asset_path, false), Default::default())
619+
IoTaskPool::init(Default::default);
620+
AssetServer::new(FileAssetIo::new(asset_path, false))
625621
}
626622

627623
#[test]

crates/bevy_asset/src/debug_asset_server.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ impl<T: Asset> Default for HandleMap<T> {
5858

5959
impl Plugin for DebugAssetServerPlugin {
6060
fn build(&self, app: &mut bevy_app::App) {
61+
IoTaskPool::init(|| {
62+
TaskPoolBuilder::default()
63+
.num_threads(2)
64+
.thread_name("Debug Asset Server IO Task Pool".to_string())
65+
.build()
66+
});
6167
let mut debug_asset_app = App::new();
6268
debug_asset_app
63-
.insert_resource(IoTaskPool(
64-
TaskPoolBuilder::default()
65-
.num_threads(2)
66-
.thread_name("Debug Asset Server IO Task Pool".to_string())
67-
.build(),
68-
))
6969
.insert_resource(AssetServerSettings {
7070
asset_folder: "crates".to_string(),
7171
watch_for_changes: true,

crates/bevy_asset/src/lib.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ pub use path::*;
3030

3131
use bevy_app::{prelude::Plugin, App};
3232
use bevy_ecs::schedule::{StageLabel, SystemStage};
33-
use bevy_tasks::IoTaskPool;
3433

3534
/// The names of asset stages in an App Schedule
3635
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
@@ -82,12 +81,8 @@ pub fn create_platform_default_asset_io(app: &mut App) -> Box<dyn AssetIo> {
8281
impl Plugin for AssetPlugin {
8382
fn build(&self, app: &mut App) {
8483
if !app.world.contains_resource::<AssetServer>() {
85-
let task_pool = app.world.resource::<IoTaskPool>().0.clone();
86-
8784
let source = create_platform_default_asset_io(app);
88-
89-
let asset_server = AssetServer::with_boxed_io(source, task_pool);
90-
85+
let asset_server = AssetServer::with_boxed_io(source);
9186
app.insert_resource(asset_server);
9287
}
9388

crates/bevy_asset/src/loader.rs

-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use crate::{
55
use anyhow::Result;
66
use bevy_ecs::system::{Res, ResMut};
77
use bevy_reflect::{TypeUuid, TypeUuidDynamic};
8-
use bevy_tasks::TaskPool;
98
use bevy_utils::{BoxedFuture, HashMap};
109
use crossbeam_channel::{Receiver, Sender};
1110
use downcast_rs::{impl_downcast, Downcast};
@@ -84,7 +83,6 @@ pub struct LoadContext<'a> {
8483
pub(crate) labeled_assets: HashMap<Option<String>, BoxedLoadedAsset>,
8584
pub(crate) path: &'a Path,
8685
pub(crate) version: usize,
87-
pub(crate) task_pool: &'a TaskPool,
8886
}
8987

9088
impl<'a> LoadContext<'a> {
@@ -93,15 +91,13 @@ impl<'a> LoadContext<'a> {
9391
ref_change_channel: &'a RefChangeChannel,
9492
asset_io: &'a dyn AssetIo,
9593
version: usize,
96-
task_pool: &'a TaskPool,
9794
) -> Self {
9895
Self {
9996
ref_change_channel,
10097
asset_io,
10198
labeled_assets: Default::default(),
10299
version,
103100
path,
104-
task_pool,
105101
}
106102
}
107103

@@ -144,10 +140,6 @@ impl<'a> LoadContext<'a> {
144140
asset_metas
145141
}
146142

147-
pub fn task_pool(&self) -> &TaskPool {
148-
self.task_pool
149-
}
150-
151143
pub fn asset_io(&self) -> &dyn AssetIo {
152144
self.asset_io
153145
}

crates/bevy_core/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl Plugin for CorePlugin {
3030
.get_resource::<DefaultTaskPoolOptions>()
3131
.cloned()
3232
.unwrap_or_default()
33-
.create_default_pools(&mut app.world);
33+
.create_default_pools();
3434

3535
app.register_type::<Entity>().register_type::<Name>();
3636

crates/bevy_core/src/task_pool_options.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use bevy_ecs::world::World;
21
use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder};
32
use bevy_utils::tracing::trace;
43

@@ -93,14 +92,14 @@ impl DefaultTaskPoolOptions {
9392
}
9493

9594
/// Inserts the default thread pools into the given resource map based on the configured values
96-
pub fn create_default_pools(&self, world: &mut World) {
95+
pub fn create_default_pools(&self) {
9796
let total_threads =
9897
bevy_tasks::logical_core_count().clamp(self.min_total_threads, self.max_total_threads);
9998
trace!("Assigning {} cores to default task pools", total_threads);
10099

101100
let mut remaining_threads = total_threads;
102101

103-
if !world.contains_resource::<IoTaskPool>() {
102+
{
104103
// Determine the number of IO threads we will use
105104
let io_threads = self
106105
.io
@@ -109,15 +108,15 @@ impl DefaultTaskPoolOptions {
109108
trace!("IO Threads: {}", io_threads);
110109
remaining_threads = remaining_threads.saturating_sub(io_threads);
111110

112-
world.insert_resource(IoTaskPool(
111+
IoTaskPool::init(|| {
113112
TaskPoolBuilder::default()
114113
.num_threads(io_threads)
115114
.thread_name("IO Task Pool".to_string())
116-
.build(),
117-
));
115+
.build()
116+
});
118117
}
119118

120-
if !world.contains_resource::<AsyncComputeTaskPool>() {
119+
{
121120
// Determine the number of async compute threads we will use
122121
let async_compute_threads = self
123122
.async_compute
@@ -126,28 +125,29 @@ impl DefaultTaskPoolOptions {
126125
trace!("Async Compute Threads: {}", async_compute_threads);
127126
remaining_threads = remaining_threads.saturating_sub(async_compute_threads);
128127

129-
world.insert_resource(AsyncComputeTaskPool(
128+
AsyncComputeTaskPool::init(|| {
130129
TaskPoolBuilder::default()
131130
.num_threads(async_compute_threads)
132131
.thread_name("Async Compute Task Pool".to_string())
133-
.build(),
134-
));
132+
.build()
133+
});
135134
}
136135

137-
if !world.contains_resource::<ComputeTaskPool>() {
136+
{
138137
// Determine the number of compute threads we will use
139138
// This is intentionally last so that an end user can specify 1.0 as the percent
140139
let compute_threads = self
141140
.compute
142141
.get_number_of_threads(remaining_threads, total_threads);
143142

144143
trace!("Compute Threads: {}", compute_threads);
145-
world.insert_resource(ComputeTaskPool(
144+
145+
ComputeTaskPool::init(|| {
146146
TaskPoolBuilder::default()
147147
.num_threads(compute_threads)
148148
.thread_name("Compute Task Pool".to_string())
149-
.build(),
150-
));
149+
.build()
150+
});
151151
}
152152
}
153153
}

crates/bevy_ecs/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,8 @@ mod tests {
375375

376376
#[test]
377377
fn par_for_each_dense() {
378+
ComputeTaskPool::init(TaskPool::default);
378379
let mut world = World::new();
379-
world.insert_resource(ComputeTaskPool(TaskPool::default()));
380380
let e1 = world.spawn().insert(A(1)).id();
381381
let e2 = world.spawn().insert(A(2)).id();
382382
let e3 = world.spawn().insert(A(3)).id();
@@ -397,8 +397,8 @@ mod tests {
397397

398398
#[test]
399399
fn par_for_each_sparse() {
400+
ComputeTaskPool::init(TaskPool::default);
400401
let mut world = World::new();
401-
world.insert_resource(ComputeTaskPool(TaskPool::default()));
402402
let e1 = world.spawn().insert(SparseStored(1)).id();
403403
let e2 = world.spawn().insert(SparseStored(2)).id();
404404
let e3 = world.spawn().insert(SparseStored(3)).id();

0 commit comments

Comments
 (0)