From f76e89582ade3dadc1c89f68d1dd6deb2062c0e8 Mon Sep 17 00:00:00 2001 From: Hans Meine Date: Sun, 4 Jun 2023 22:03:04 +0200 Subject: [PATCH 1/9] elaborate on TaskPool and bevy tasks I found it very difficult to understand how bevy tasks work, and these changes to the documentation were written from my beginner's perspective after some extremely helpful explanations by nil on Discord. --- crates/bevy_tasks/src/task_pool.rs | 9 +++++++-- crates/bevy_tasks/src/usages.rs | 9 ++++++--- examples/async_tasks/async_compute.rs | 4 +++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 5903e67ea1205..6c2087d3f5e38 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -93,8 +93,13 @@ impl TaskPoolBuilder { } } -/// A thread pool for executing tasks. Tasks are futures that are being automatically driven by -/// the pool on threads owned by the pool. +/// A thread pool for executing tasks. While futures usually need to be polled +/// to be executed, Bevy tasks are being automatically driven by the pool on +/// threads owned by the pool. The [`Task`] future only needs to be polled in +/// order to receive the result. (For that purpose, it is often stored in a +/// component or resource, see the `async_compute` example.) If the result is +/// not required, one may also use [`Task.detach`] and the pool will still +/// execute a task, even if it is dropped. #[derive(Debug)] pub struct TaskPool { /// The executor for the pool diff --git a/crates/bevy_tasks/src/usages.rs b/crates/bevy_tasks/src/usages.rs index 0ba38eb58c730..6d3fe448659aa 100644 --- a/crates/bevy_tasks/src/usages.rs +++ b/crates/bevy_tasks/src/usages.rs @@ -17,8 +17,10 @@ static COMPUTE_TASK_POOL: OnceLock = OnceLock::new(); static ASYNC_COMPUTE_TASK_POOL: OnceLock = OnceLock::new(); static IO_TASK_POOL: OnceLock = OnceLock::new(); -/// A newtype for a task pool for CPU-intensive work that must be completed to deliver the next -/// frame +/// A newtype for a task pool for CPU-intensive work that must be completed to +/// deliver the next frame. See [`TaskPool`] documentation for details on Bevy +/// tasks. ['AsyncComputeTaskPool'] should be preferred if the work does not +/// have to be completed before the next frame. #[derive(Debug)] pub struct ComputeTaskPool(TaskPool); @@ -48,7 +50,8 @@ impl Deref for ComputeTaskPool { } } -/// A newtype for a task pool for CPU-intensive work that may span across multiple frames +/// A newtype for a task pool for CPU-intensive work that may span across multiple frames. +/// See [`TaskPool`] documentation for details on Bevy tasks. Use [`ComputeTaskPool`] if #[derive(Debug)] pub struct AsyncComputeTaskPool(TaskPool); diff --git a/examples/async_tasks/async_compute.rs b/examples/async_tasks/async_compute.rs index 57f9ea9fa4f44..4c081faf2d05d 100644 --- a/examples/async_tasks/async_compute.rs +++ b/examples/async_tasks/async_compute.rs @@ -54,7 +54,9 @@ fn spawn_tasks(mut commands: Commands) { for x in 0..NUM_CUBES { for y in 0..NUM_CUBES { for z in 0..NUM_CUBES { - // Spawn new task on the AsyncComputeTaskPool + // Spawn new task on the AsyncComputeTaskPool; the task will be + // executed in th background, and the Task future returned by + // spawn() can be used to poll for the result let task = thread_pool.spawn(async move { let mut rng = rand::thread_rng(); let start_time = Instant::now(); From 96462e16e2cb1a1d4a2a830036bd8c8976877bbc Mon Sep 17 00:00:00 2001 From: Hans Meine Date: Sun, 4 Jun 2023 22:44:42 +0200 Subject: [PATCH 2/9] fix formatting for class docs (summary first) --- crates/bevy_tasks/src/task_pool.rs | 17 ++++++++++------- crates/bevy_tasks/src/usages.rs | 16 ++++++++++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 6c2087d3f5e38..6966ab1d2da69 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -93,13 +93,16 @@ impl TaskPoolBuilder { } } -/// A thread pool for executing tasks. While futures usually need to be polled -/// to be executed, Bevy tasks are being automatically driven by the pool on -/// threads owned by the pool. The [`Task`] future only needs to be polled in -/// order to receive the result. (For that purpose, it is often stored in a -/// component or resource, see the `async_compute` example.) If the result is -/// not required, one may also use [`Task.detach`] and the pool will still -/// execute a task, even if it is dropped. +/// A thread pool for executing tasks. +/// +/// While futures usually need to be polled to be executed, Bevy tasks are being +/// automatically driven by the pool on threads owned by the pool. The [`Task`] +/// future only needs to be polled in order to receive the result. (For that +/// purpose, it is often stored in a component or resource, see the +/// `async_compute` example.) +/// +/// If the result is not required, one may also use [`Task.detach`] and the pool +/// will still execute a task, even if it is dropped. #[derive(Debug)] pub struct TaskPool { /// The executor for the pool diff --git a/crates/bevy_tasks/src/usages.rs b/crates/bevy_tasks/src/usages.rs index 6d3fe448659aa..0e8e987c3cd79 100644 --- a/crates/bevy_tasks/src/usages.rs +++ b/crates/bevy_tasks/src/usages.rs @@ -2,8 +2,8 @@ //! kind of work should go in each pool is latency requirements. //! //! 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`] +//! [`ComputeTaskPool`] and an [`AsyncComputeTaskPool`]. Work that does not need to be completed to +//! present the next frame should go to the [`AsyncComputeTaskPool`]. //! //! 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 @@ -18,9 +18,11 @@ static ASYNC_COMPUTE_TASK_POOL: OnceLock = OnceLock::new() static IO_TASK_POOL: OnceLock = OnceLock::new(); /// A newtype for a task pool for CPU-intensive work that must be completed to -/// deliver the next frame. See [`TaskPool`] documentation for details on Bevy -/// tasks. ['AsyncComputeTaskPool'] should be preferred if the work does not -/// have to be completed before the next frame. +/// deliver the next frame. +/// +/// See [`TaskPool`] documentation for details on Bevy tasks. +/// ['AsyncComputeTaskPool'] should be preferred if the work does not have to be +/// completed before the next frame. #[derive(Debug)] pub struct ComputeTaskPool(TaskPool); @@ -51,7 +53,9 @@ impl Deref for ComputeTaskPool { } /// A newtype for a task pool for CPU-intensive work that may span across multiple frames. -/// See [`TaskPool`] documentation for details on Bevy tasks. Use [`ComputeTaskPool`] if +/// +/// See [`TaskPool`] documentation for details on Bevy tasks. Use [`ComputeTaskPool`] if +/// the work must be complete before advancing to the next frame. #[derive(Debug)] pub struct AsyncComputeTaskPool(TaskPool); From f6bdbd11fabbb405ad4b80eecd8eb20881830a82 Mon Sep 17 00:00:00 2001 From: Hans Meine Date: Sun, 4 Jun 2023 22:44:59 +0200 Subject: [PATCH 3/9] try to fix misleading taskpool spawn() docs --- crates/bevy_tasks/src/task_pool.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 6966ab1d2da69..06ad7e23c8afd 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -517,11 +517,14 @@ impl TaskPool { execute_forever.or(get_results).await } - /// Spawns a static future onto the thread pool. The returned Task is a future. It can also be - /// canceled and "detached" allowing it to continue running without having to be polled by the + /// Spawns a static future onto the thread pool. The returned [`Task`] is a + /// future that can be polled for the result. It can also be canceled and + /// "detached", allowing the task to continue running even if dropped. In + /// any case, the pool will execute the task even without polling by the /// end-user. /// - /// If the provided future is non-`Send`, [`TaskPool::spawn_local`] should be used instead. + /// If the provided future is non-`Send`, [`TaskPool::spawn_local`] should + /// be used instead. pub fn spawn(&self, future: impl Future + Send + 'static) -> Task where T: Send + 'static, @@ -529,11 +532,17 @@ impl TaskPool { Task::new(self.executor.spawn(future)) } - /// Spawns a static future on the thread-local async executor for the current thread. The task - /// will run entirely on the thread the task was spawned on. The returned Task is a future. - /// It can also be canceled and "detached" allowing it to continue running without having - /// to be polled by the end-user. Users should generally prefer to use [`TaskPool::spawn`] - /// instead, unless the provided future is not `Send`. + /// Spawns a static future on the thread-local async executor for the + /// current thread. The task will run entirely on the thread the task was + /// spawned on. + /// + /// The returned [`Task`] is a future that can be polled for the + /// result. It can also be canceled and "detached", allowing the task to + /// continue running even if dropped. In any case, the pool will execute the + /// task even without polling by the end-user. + /// + /// Users should generally prefer to use [`TaskPool::spawn`] instead, + /// unless the provided future is not `Send`. pub fn spawn_local(&self, future: impl Future + 'static) -> Task where T: 'static, From a19f5447da0d151b78d1ef33650e67f97b4c96db Mon Sep 17 00:00:00 2001 From: Hans Meine Date: Sun, 4 Jun 2023 22:47:53 +0200 Subject: [PATCH 4/9] fix trailing spaces in newly introduced lines --- crates/bevy_tasks/src/task_pool.rs | 8 ++++---- crates/bevy_tasks/src/usages.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 06ad7e23c8afd..5d10ae3d7f7b7 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -94,13 +94,13 @@ impl TaskPoolBuilder { } /// A thread pool for executing tasks. -/// +/// /// While futures usually need to be polled to be executed, Bevy tasks are being /// automatically driven by the pool on threads owned by the pool. The [`Task`] /// future only needs to be polled in order to receive the result. (For that /// purpose, it is often stored in a component or resource, see the /// `async_compute` example.) -/// +/// /// If the result is not required, one may also use [`Task.detach`] and the pool /// will still execute a task, even if it is dropped. #[derive(Debug)] @@ -535,12 +535,12 @@ impl TaskPool { /// Spawns a static future on the thread-local async executor for the /// current thread. The task will run entirely on the thread the task was /// spawned on. - /// + /// /// The returned [`Task`] is a future that can be polled for the /// result. It can also be canceled and "detached", allowing the task to /// continue running even if dropped. In any case, the pool will execute the /// task even without polling by the end-user. - /// + /// /// Users should generally prefer to use [`TaskPool::spawn`] instead, /// unless the provided future is not `Send`. pub fn spawn_local(&self, future: impl Future + 'static) -> Task diff --git a/crates/bevy_tasks/src/usages.rs b/crates/bevy_tasks/src/usages.rs index 0e8e987c3cd79..350e8b6830ba9 100644 --- a/crates/bevy_tasks/src/usages.rs +++ b/crates/bevy_tasks/src/usages.rs @@ -19,7 +19,7 @@ static IO_TASK_POOL: OnceLock = OnceLock::new(); /// A newtype for a task pool for CPU-intensive work that must be completed to /// deliver the next frame. -/// +/// /// See [`TaskPool`] documentation for details on Bevy tasks. /// ['AsyncComputeTaskPool'] should be preferred if the work does not have to be /// completed before the next frame. @@ -53,7 +53,7 @@ impl Deref for ComputeTaskPool { } /// A newtype for a task pool for CPU-intensive work that may span across multiple frames. -/// +/// /// See [`TaskPool`] documentation for details on Bevy tasks. Use [`ComputeTaskPool`] if /// the work must be complete before advancing to the next frame. #[derive(Debug)] From 72f0dded348690bdbc1e0167b11df4d0574b57f2 Mon Sep 17 00:00:00 2001 From: Hans Meine Date: Mon, 5 Jun 2023 11:35:47 +0200 Subject: [PATCH 5/9] pull useful internal documentation to top level The non-public `usages` module contained useful documentation that is missing from the published docs, and that is in fact good for the `bevy_tasks` front page. --- crates/bevy_tasks/README.md | 14 ++++++++++++++ crates/bevy_tasks/src/usages.rs | 14 +------------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/bevy_tasks/README.md b/crates/bevy_tasks/README.md index 523dbff7eaba9..62eab0b5c96b3 100644 --- a/crates/bevy_tasks/README.md +++ b/crates/bevy_tasks/README.md @@ -11,6 +11,20 @@ or ordering of spawned tasks. It is based on [`async-executor`][async-executor], a lightweight executor that allows the end user to manage their own threads. `async-executor` is based on async-task, a core piece of async-std. +## Usage + +Tasks are spawned via thread pools, of which bevy_tasks provides three different ones for different kinds of tasks. +The determining factor for what kind of work should go in each pool is latency requirements: + +* 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`]. + +* For IO-intensive work (tasks that spend very little time in a "woken" state) we have an + [`IoTaskPool`] whose tasks are expected to complete very quickly. Generally speaking, 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) + [bevy]: https://bevyengine.org [rayon]: https://github.com/rayon-rs/rayon [async-executor]: https://github.com/stjepang/async-executor diff --git a/crates/bevy_tasks/src/usages.rs b/crates/bevy_tasks/src/usages.rs index 350e8b6830ba9..45c9b9e422eb5 100644 --- a/crates/bevy_tasks/src/usages.rs +++ b/crates/bevy_tasks/src/usages.rs @@ -1,15 +1,3 @@ -//! 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. -//! -//! 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`]. -//! -//! 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; use std::{ops::Deref, sync::OnceLock}; @@ -21,7 +9,7 @@ static IO_TASK_POOL: OnceLock = OnceLock::new(); /// deliver the next frame. /// /// See [`TaskPool`] documentation for details on Bevy tasks. -/// ['AsyncComputeTaskPool'] should be preferred if the work does not have to be +/// [`AsyncComputeTaskPool`] should be preferred if the work does not have to be /// completed before the next frame. #[derive(Debug)] pub struct ComputeTaskPool(TaskPool); From 100bb1881f5a70e494af693afc98e2d4df2681c9 Mon Sep 17 00:00:00 2001 From: Hans Meine Date: Tue, 6 Jun 2023 17:01:18 +0200 Subject: [PATCH 6/9] fix typo noticed by DanielHZhang --- examples/async_tasks/async_compute.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/async_tasks/async_compute.rs b/examples/async_tasks/async_compute.rs index 4c081faf2d05d..7ab793809776e 100644 --- a/examples/async_tasks/async_compute.rs +++ b/examples/async_tasks/async_compute.rs @@ -55,7 +55,7 @@ fn spawn_tasks(mut commands: Commands) { for y in 0..NUM_CUBES { for z in 0..NUM_CUBES { // Spawn new task on the AsyncComputeTaskPool; the task will be - // executed in th background, and the Task future returned by + // executed in the background, and the Task future returned by // spawn() can be used to poll for the result let task = thread_pool.spawn(async move { let mut rng = rand::thread_rng(); From 5eb53461cc7fbe17964473d0c1d3525da6cd4a68 Mon Sep 17 00:00:00 2001 From: Hans Meine Date: Sun, 11 Jun 2023 15:35:45 +0200 Subject: [PATCH 7/9] revert periods at the end of struct doc summaries I noticed that I accidentally "fixed" what I thought were missing full stops after the summary phrase / paragraph at the beginning of the pool struct documentation. However, these summaries appear on the top level bevy_tasks docs in some kind of table, where those periods would not be desirable. --- crates/bevy_tasks/src/usages.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_tasks/src/usages.rs b/crates/bevy_tasks/src/usages.rs index 45c9b9e422eb5..49b8b5cd2ff72 100644 --- a/crates/bevy_tasks/src/usages.rs +++ b/crates/bevy_tasks/src/usages.rs @@ -6,7 +6,7 @@ static ASYNC_COMPUTE_TASK_POOL: OnceLock = OnceLock::new() static IO_TASK_POOL: OnceLock = OnceLock::new(); /// A newtype for a task pool for CPU-intensive work that must be completed to -/// deliver the next frame. +/// deliver the next frame /// /// See [`TaskPool`] documentation for details on Bevy tasks. /// [`AsyncComputeTaskPool`] should be preferred if the work does not have to be @@ -40,7 +40,7 @@ impl Deref for ComputeTaskPool { } } -/// A newtype for a task pool for CPU-intensive work that may span across multiple frames. +/// A newtype for a task pool for CPU-intensive work that may span across multiple frames /// /// See [`TaskPool`] documentation for details on Bevy tasks. Use [`ComputeTaskPool`] if /// the work must be complete before advancing to the next frame. From 90fc3d410cabc45d1ded743b1244709d417a75cd Mon Sep 17 00:00:00 2001 From: Hans Meine Date: Sun, 30 Jul 2023 17:05:35 +0200 Subject: [PATCH 8/9] elaborate on single-threaded environments (WASM) --- crates/bevy_tasks/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bevy_tasks/README.md b/crates/bevy_tasks/README.md index 62eab0b5c96b3..881d7b802a50b 100644 --- a/crates/bevy_tasks/README.md +++ b/crates/bevy_tasks/README.md @@ -13,7 +13,10 @@ It is based on [`async-executor`][async-executor], a lightweight executor that a ## Usage -Tasks are spawned via thread pools, of which bevy_tasks provides three different ones for different kinds of tasks. +In order to be able to optimize task execution in multi-threaded environments, +bevy provides three different thread pools via which tasks of different kinds can be spawned. +(The same API is used in single-threaded environments, even if execution is limited to a single thread. +This currently applies to WASM targets.) The determining factor for what kind of work should go in each pool is latency requirements: * For CPU-intensive work (tasks that generally spin until completion) we have a standard From e033b1650ce3786d78cba228194006894d1f0074 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 11 Aug 2023 09:10:31 -0400 Subject: [PATCH 9/9] Rust uses :: for method namespacing Co-authored-by: Mike --- crates/bevy_tasks/src/task_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 5d10ae3d7f7b7..90afe4b4bc2e3 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -101,7 +101,7 @@ impl TaskPoolBuilder { /// purpose, it is often stored in a component or resource, see the /// `async_compute` example.) /// -/// If the result is not required, one may also use [`Task.detach`] and the pool +/// If the result is not required, one may also use [`Task::detach`] and the pool /// will still execute a task, even if it is dropped. #[derive(Debug)] pub struct TaskPool {