2020-08-29 19:35:41 +00:00
|
|
|
//! Definitions for a few common task pools that we want. Generally the determining factor for what
|
|
|
|
//! kind of work should go in each pool is latency requirements.
|
|
|
|
//!
|
2021-12-29 17:38:13 +00:00
|
|
|
//! For CPU-intensive work (tasks that generally spin until completion) we have a standard
|
|
|
|
//! [`ComputeTaskPool`] and an [`AsyncComputeTaskPool`]. Work that does not need to be completed to
|
|
|
|
//! present the next frame should go to the [`AsyncComputeTaskPool`]
|
2020-08-29 19:35:41 +00:00
|
|
|
//!
|
|
|
|
//! For IO-intensive work (tasks that spend very little time in a "woken" state) we have an IO
|
|
|
|
//! task pool. The tasks here are expected to complete very quickly. Generally they should just
|
|
|
|
//! await receiving data from somewhere (i.e. disk) and signal other systems when the data is ready
|
|
|
|
//! for consumption. (likely via channels)
|
|
|
|
|
|
|
|
use super::TaskPool;
|
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`.
2022-06-09 02:43:24 +00:00
|
|
|
use once_cell::sync::OnceCell;
|
2020-08-29 19:35:41 +00:00
|
|
|
use std::ops::Deref;
|
|
|
|
|
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`.
2022-06-09 02:43:24 +00:00
|
|
|
static COMPUTE_TASK_POOL: OnceCell<ComputeTaskPool> = OnceCell::new();
|
|
|
|
static ASYNC_COMPUTE_TASK_POOL: OnceCell<AsyncComputeTaskPool> = OnceCell::new();
|
|
|
|
static IO_TASK_POOL: OnceCell<IoTaskPool> = OnceCell::new();
|
|
|
|
|
2020-08-29 19:35:41 +00:00
|
|
|
/// A newtype for a task pool for CPU-intensive work that must be completed to deliver the next
|
|
|
|
/// frame
|
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`.
2022-06-09 02:43:24 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ComputeTaskPool(TaskPool);
|
|
|
|
|
|
|
|
impl ComputeTaskPool {
|
|
|
|
/// Initializes the global [`ComputeTaskPool`] instance.
|
|
|
|
pub fn init(f: impl FnOnce() -> TaskPool) -> &'static Self {
|
|
|
|
COMPUTE_TASK_POOL.get_or_init(|| Self(f()))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the global [`ComputeTaskPool`] instance.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
/// Panics if no pool has been initialized yet.
|
|
|
|
pub fn get() -> &'static Self {
|
|
|
|
COMPUTE_TASK_POOL.get().expect(
|
|
|
|
"A ComputeTaskPool has not been initialized yet. Please call \
|
|
|
|
ComputeTaskPool::init beforehand.",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2020-08-29 19:35:41 +00:00
|
|
|
|
|
|
|
impl Deref for ComputeTaskPool {
|
|
|
|
type Target = TaskPool;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A newtype for a task pool for CPU-intensive work that may span across multiple frames
|
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`.
2022-06-09 02:43:24 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct AsyncComputeTaskPool(TaskPool);
|
|
|
|
|
|
|
|
impl AsyncComputeTaskPool {
|
|
|
|
/// Initializes the global [`AsyncComputeTaskPool`] instance.
|
|
|
|
pub fn init(f: impl FnOnce() -> TaskPool) -> &'static Self {
|
|
|
|
ASYNC_COMPUTE_TASK_POOL.get_or_init(|| Self(f()))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the global [`AsyncComputeTaskPool`] instance.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
/// Panics if no pool has been initialized yet.
|
|
|
|
pub fn get() -> &'static Self {
|
|
|
|
ASYNC_COMPUTE_TASK_POOL.get().expect(
|
|
|
|
"A AsyncComputeTaskPool has not been initialized yet. Please call \
|
|
|
|
AsyncComputeTaskPool::init beforehand.",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2020-08-29 19:35:41 +00:00
|
|
|
|
|
|
|
impl Deref for AsyncComputeTaskPool {
|
|
|
|
type Target = TaskPool;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A newtype for a task pool for IO-intensive work (i.e. tasks that spend very little time in a
|
|
|
|
/// "woken" state)
|
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`.
2022-06-09 02:43:24 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct IoTaskPool(TaskPool);
|
|
|
|
|
|
|
|
impl IoTaskPool {
|
|
|
|
/// Initializes the global [`IoTaskPool`] instance.
|
|
|
|
pub fn init(f: impl FnOnce() -> TaskPool) -> &'static Self {
|
|
|
|
IO_TASK_POOL.get_or_init(|| Self(f()))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the global [`IoTaskPool`] instance.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
/// Panics if no pool has been initialized yet.
|
|
|
|
pub fn get() -> &'static Self {
|
|
|
|
IO_TASK_POOL.get().expect(
|
|
|
|
"A IoTaskPool has not been initialized yet. Please call \
|
|
|
|
IoTaskPool::init beforehand.",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2020-08-29 19:35:41 +00:00
|
|
|
|
2020-09-22 03:23:09 +00:00
|
|
|
impl Deref for IoTaskPool {
|
2020-08-29 19:35:41 +00:00
|
|
|
type Target = TaskPool;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
2022-10-24 13:46:40 +00:00
|
|
|
|
|
|
|
/// Used by `bevy_core` to tick the global tasks pools on the main thread.
|
|
|
|
/// This will run a maximum of 100 local tasks per executor per call to this function.
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
pub fn tick_global_task_pools_on_main_thread() {
|
|
|
|
COMPUTE_TASK_POOL
|
|
|
|
.get()
|
|
|
|
.unwrap()
|
|
|
|
.with_local_executor(|compute_local_executor| {
|
|
|
|
ASYNC_COMPUTE_TASK_POOL
|
|
|
|
.get()
|
|
|
|
.unwrap()
|
|
|
|
.with_local_executor(|async_local_executor| {
|
|
|
|
IO_TASK_POOL
|
|
|
|
.get()
|
|
|
|
.unwrap()
|
|
|
|
.with_local_executor(|io_local_executor| {
|
|
|
|
for _ in 0..100 {
|
|
|
|
compute_local_executor.try_tick();
|
|
|
|
async_local_executor.try_tick();
|
|
|
|
io_local_executor.try_tick();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|