From fa906daa12270e41c677b6d69e554378f7a1d3b4 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 18 Nov 2022 19:13:10 -0800 Subject: [PATCH 01/17] Add feature for disabling parallel executors in bevy_ecs --- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/query/state.rs | 24 ++++++++++++++++++++++++ crates/bevy_ecs/src/schedule/stage.rs | 13 +++++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 000c2121a7d1c..dded9c92cf0e4 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -11,6 +11,7 @@ categories = ["game-engines", "data-structures"] [features] trace = [] +single-threaded = [] default = ["bevy_reflect"] [dependencies] diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 245ff7dacb7b4..c10ef1fd39fcc 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -833,6 +833,28 @@ impl QueryState { ); } + #[inline] + #[cfg(feature = "single-threaded")] + pub fn par_for_each<'w, FN: Fn(ROQueryItem<'w, Q>) + Send + Sync + Clone>( + &mut self, + world: &'w World, + _batch_size: usize, + func: FN, + ) { + self.for_each(world, func); + } + + #[inline] + #[cfg(feature = "single-threaded")] + pub fn par_for_each_mut<'w, FN: Fn(Q::Item<'w>) + Send + Sync + Clone>( + &mut self, + world: &'w mut World, + _batch_size: usize, + func: FN, + ) { + self.for_each_mut(world, func); + } + /// Runs `func` on each query result in parallel. /// /// This can only be called for read-only queries, see [`Self::par_for_each_mut`] for @@ -842,6 +864,7 @@ impl QueryState { /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being /// initialized and run from the ECS scheduler, this should never panic. #[inline] + #[cfg(not(feature = "single-threaded"))] pub fn par_for_each<'w, FN: Fn(ROQueryItem<'w, Q>) + Send + Sync + Clone>( &mut self, world: &'w World, @@ -867,6 +890,7 @@ impl QueryState { /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being /// initialized and run from the ECS scheduler, this should never panic. #[inline] + #[cfg(not(feature = "single-threaded"))] pub fn par_for_each_mut<'w, FN: Fn(Q::Item<'w>) + Send + Sync + Clone>( &mut self, world: &'w mut World, diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 238743965d7ef..51b41a9c8e00c 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -6,12 +6,14 @@ use crate::{ schedule::{ graph_utils::{self, DependencyGraphError}, BoxedRunCriteria, DuplicateLabelStrategy, ExclusiveInsertionPoint, GraphNode, - ParallelExecutor, ParallelSystemExecutor, RunCriteriaContainer, RunCriteriaDescriptor, + ParallelSystemExecutor, RunCriteriaContainer, RunCriteriaDescriptor, RunCriteriaDescriptorOrLabel, RunCriteriaInner, RunCriteriaLabelId, ShouldRun, SingleThreadedExecutor, SystemContainer, SystemDescriptor, SystemLabelId, SystemSet, }, world::{World, WorldId}, }; +#[cfg(not(feature = "single-threaded"))] +use crate::schedule::ParallelExecutor; use bevy_ecs_macros::Resource; use bevy_utils::{tracing::warn, HashMap, HashSet}; use core::fmt::Debug; @@ -136,7 +138,14 @@ impl SystemStage { } pub fn parallel() -> Self { - Self::new(Box::::default()) + #[cfg(not(feature = "single-threaded"))] + { + Self::new(Box::::default()) + } + #[cfg(feature = "single-threaded")] + { + Self::single_threaded() + } } pub fn get_executor(&self) -> Option<&T> { From b54b0e91bdceebd5aad641d365155335631d880f Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 18 Nov 2022 19:27:35 -0800 Subject: [PATCH 02/17] Add single threaded TaskPool that isn't dependent on wasm_bindgen_futures --- crates/bevy_tasks/Cargo.toml | 4 + crates/bevy_tasks/src/lib.rs | 11 +- .../src/single_threaded_task_pool.rs | 43 +++-- .../src/wasm_single_threaded_task_pool.rs | 173 ++++++++++++++++++ 4 files changed, 217 insertions(+), 14 deletions(-) create mode 100644 crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index f86fb78d3bc23..e7a7c628e83c1 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -8,6 +8,10 @@ repository = "https://github.com/bevyengine/bevy" license = "MIT OR Apache-2.0" keywords = ["bevy"] +[features] +single-threaded = [] +default = [] + [dependencies] futures-lite = "1.4.0" async-executor = "1.3.0" diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index 802f6c267b7cf..d86d188c2def3 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -7,15 +7,20 @@ pub use slice::{ParallelSlice, ParallelSliceMut}; mod task; pub use task::Task; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(feature = "single-threaded")))] mod task_pool; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(feature = "single-threaded")))] pub use task_pool::{Scope, TaskPool, TaskPoolBuilder}; +#[cfg(all(not(target_arch = "wasm32"), feature = "single-threaded"))] +mod single_threaded_task_pool; +#[cfg(all(not(target_arch = "wasm32"), feature = "single-threaded"))] +pub use single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder}; + #[cfg(target_arch = "wasm32")] mod single_threaded_task_pool; #[cfg(target_arch = "wasm32")] -pub use single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder}; +pub use wasm_single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder}; mod usages; #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index 8fa37f4f2361b..5760257f2e257 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -5,6 +5,10 @@ use std::{ sync::{Arc, Mutex}, }; +thread_local! { + static LOCAL_EXECUTOR: async_executor::LocalExecutor<'static> = async_executor::LocalExecutor::new(); +} + /// Used to create a TaskPool #[derive(Debug, Default, Clone)] pub struct TaskPoolBuilder {} @@ -95,22 +99,21 @@ impl TaskPool { .collect() } - /// Spawns a static future onto the JS event loop. For now it is returning FakeTask - /// instance with no-op detach method. Returning real Task is possible here, but tricky: - /// future is running on JS event loop, Task is running on async_executor::LocalExecutor - /// so some proxy future is needed. Moreover currently we don't have long-living - /// LocalExecutor here (above `spawn` implementation creates temporary one) - /// But for typical use cases it seems that current implementation should be sufficient: - /// caller can spawn long-running future writing results to some channel / event queue - /// and simply call detach on returned Task (like AssetServer does) - spawned future - /// can write results to some channel / event queue. + /// Spawns a static future onto the thread pool. The returned Task is a future. It can also be + /// cancelled and "detached" allowing it to continue running without having to be polled by the + /// end-user. + /// + /// If the provided future is non-`Send`, [`TaskPool::spawn_local`] should be used instead. pub fn spawn(&self, future: impl Future + 'static) -> FakeTask where T: 'static, { - wasm_bindgen_futures::spawn_local(async move { - future.await; + LOCAL_EXECUTOR.with(|executor|{ + let _task = executor.spawn(future); + // Loop until all tasks are done + while executor.try_tick() {} }); + FakeTask } @@ -121,6 +124,24 @@ impl TaskPool { { self.spawn(future) } + + /// Runs a function with the local executor. Typically used to tick + /// the local executor on the main thread as it needs to share time with + /// other things. + /// + /// ```rust + /// use bevy_tasks::TaskPool; + /// + /// TaskPool::new().with_local_executor(|local_executor| { + /// local_executor.try_tick(); + /// }); + /// ``` + pub fn with_local_executor(&self, f: F) -> R + where + F: FnOnce(&async_executor::LocalExecutor) -> R, + { + LOCAL_EXECUTOR.with(f) + } } #[derive(Debug)] diff --git a/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs b/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs new file mode 100644 index 0000000000000..8fa37f4f2361b --- /dev/null +++ b/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs @@ -0,0 +1,173 @@ +use std::{ + future::Future, + marker::PhantomData, + mem, + sync::{Arc, Mutex}, +}; + +/// Used to create a TaskPool +#[derive(Debug, Default, Clone)] +pub struct TaskPoolBuilder {} + +impl TaskPoolBuilder { + /// Creates a new TaskPoolBuilder instance + pub fn new() -> Self { + Self::default() + } + + /// No op on the single threaded task pool + pub fn num_threads(self, _num_threads: usize) -> Self { + self + } + + /// No op on the single threaded task pool + pub fn stack_size(self, _stack_size: usize) -> Self { + self + } + + /// No op on the single threaded task pool + pub fn thread_name(self, _thread_name: String) -> Self { + self + } + + /// Creates a new [`TaskPool`] + pub fn build(self) -> TaskPool { + TaskPool::new_internal() + } +} + +/// A thread pool for executing tasks. Tasks are futures that are being automatically driven by +/// the pool on threads owned by the pool. In this case - main thread only. +#[derive(Debug, Default, Clone)] +pub struct TaskPool {} + +impl TaskPool { + /// Create a `TaskPool` with the default configuration. + pub fn new() -> Self { + TaskPoolBuilder::new().build() + } + + #[allow(unused_variables)] + fn new_internal() -> Self { + Self {} + } + + /// Return the number of threads owned by the task pool + pub fn thread_num(&self) -> usize { + 1 + } + + /// Allows spawning non-`static futures on the thread pool. The function takes a callback, + /// passing a scope object into it. The scope object provided to the callback can be used + /// to spawn tasks. This function will await the completion of all tasks before returning. + /// + /// This is similar to `rayon::scope` and `crossbeam::scope` + pub fn scope<'env, F, T>(&self, f: F) -> Vec + where + F: for<'scope> FnOnce(&'env mut Scope<'scope, 'env, T>), + T: Send + 'static, + { + let executor = &async_executor::LocalExecutor::new(); + let executor: &'env async_executor::LocalExecutor<'env> = + unsafe { mem::transmute(executor) }; + + let results: Mutex>>>> = Mutex::new(Vec::new()); + let results: &'env Mutex>>>> = unsafe { mem::transmute(&results) }; + + let mut scope = Scope { + executor, + results, + scope: PhantomData, + env: PhantomData, + }; + + let scope_ref: &'env mut Scope<'_, 'env, T> = unsafe { mem::transmute(&mut scope) }; + + f(scope_ref); + + // Loop until all tasks are done + while executor.try_tick() {} + + let results = scope.results.lock().unwrap(); + results + .iter() + .map(|result| result.lock().unwrap().take().unwrap()) + .collect() + } + + /// Spawns a static future onto the JS event loop. For now it is returning FakeTask + /// instance with no-op detach method. Returning real Task is possible here, but tricky: + /// future is running on JS event loop, Task is running on async_executor::LocalExecutor + /// so some proxy future is needed. Moreover currently we don't have long-living + /// LocalExecutor here (above `spawn` implementation creates temporary one) + /// But for typical use cases it seems that current implementation should be sufficient: + /// caller can spawn long-running future writing results to some channel / event queue + /// and simply call detach on returned Task (like AssetServer does) - spawned future + /// can write results to some channel / event queue. + pub fn spawn(&self, future: impl Future + 'static) -> FakeTask + where + T: 'static, + { + wasm_bindgen_futures::spawn_local(async move { + future.await; + }); + FakeTask + } + + /// Spawns a static future on the JS event loop. This is exactly the same as [`TaskSpool::spawn`]. + pub fn spawn_local(&self, future: impl Future + 'static) -> FakeTask + where + T: 'static, + { + self.spawn(future) + } +} + +#[derive(Debug)] +pub struct FakeTask; + +impl FakeTask { + /// No op on the single threaded task pool + pub fn detach(self) {} +} + +/// A `TaskPool` scope for running one or more non-`'static` futures. +/// +/// For more information, see [`TaskPool::scope`]. +#[derive(Debug)] +pub struct Scope<'scope, 'env: 'scope, T> { + executor: &'env async_executor::LocalExecutor<'env>, + // Vector to gather results of all futures spawned during scope run + results: &'env Mutex>>>>, + + // make `Scope` invariant over 'scope and 'env + scope: PhantomData<&'scope mut &'scope ()>, + env: PhantomData<&'env mut &'env ()>, +} + +impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> { + /// Spawns a scoped future onto the thread-local executor. The scope *must* outlive + /// the provided future. The results of the future will be returned as a part of + /// [`TaskPool::scope`]'s return value. + /// + /// On the single threaded task pool, it just calls [`Scope::spawn_local`]. + /// + /// For more information, see [`TaskPool::scope`]. + pub fn spawn + 'env>(&self, f: Fut) { + self.spawn_on_scope(f); + } + + /// Spawns a scoped future that runs on the thread the scope called from. The + /// scope *must* outlive the provided future. The results of the future will be + /// returned as a part of [`TaskPool::scope`]'s return value. + /// + /// For more information, see [`TaskPool::scope`]. + pub fn spawn_on_scope + 'env>(&self, f: Fut) { + let result = Arc::new(Mutex::new(None)); + self.results.lock().unwrap().push(result.clone()); + let f = async move { + result.lock().unwrap().replace(f.await); + }; + self.executor.spawn(f).detach(); + } +} From cb2a7468016e8981970741d708976a9fd5a75ec3 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 18 Nov 2022 19:37:34 -0800 Subject: [PATCH 03/17] Bubble up features to bevy_internal and bevy --- Cargo.toml | 3 +++ crates/bevy_internal/Cargo.toml | 1 + docs/cargo_features.md | 1 + 3 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 251e47609645d..033ac20ec0dda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,9 @@ filesystem_watcher = ["bevy_internal/filesystem_watcher"] serialize = ["bevy_internal/serialize"] +# Disables all parallelism in the engine. Forces all engine tasks to run on a single thread. +single-threaded = ["bevy_internal/single-threaded"] + # Display server protocol support (X11 is enabled by default) wayland = ["bevy_internal/wayland"] x11 = ["bevy_internal/x11"] diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index ac371a3d4a82a..4d014c39ede93 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -47,6 +47,7 @@ wav = ["bevy_audio/wav"] filesystem_watcher = ["bevy_asset/filesystem_watcher"] serialize = ["bevy_core/serialize", "bevy_input/serialize", "bevy_time/serialize", "bevy_window/serialize", "bevy_transform/serialize", "bevy_math/serialize", "bevy_scene/serialize"] +single-threaded = ["bevy_ecs/single-threaded", "bevy_tasks/single-threaded"] # Display server protocol support (X11 is enabled by default) wayland = ["bevy_winit/wayland"] diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 8d0db4604380e..ba972c58bdb88 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -40,6 +40,7 @@ |mp3|MP3 audio format support.| |wav|WAV audio format support.| |serialize|Enables serialization of `bevy_input` types.| +|single-threaded|Disables all parallelism in the engine. All engine tasks run on a single thread. Does nothing on WASM.| |wayland|Enable this to use Wayland display server protocol other than X11.| |subpixel_glyph_atlas|Enable this to cache glyphs using subpixel accuracy. This increases texture memory usage as each position requires a separate sprite in the glyph atlas, but provide more accurate character spacing.| |bevy_ci_testing|Used for running examples in CI.| From 687e046fb0405c6242d5898494daf95bfeff3c56 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 18 Nov 2022 19:51:13 -0800 Subject: [PATCH 04/17] Add missing Query cfg blocks --- crates/bevy_ecs/src/schedule/stage.rs | 4 ++-- crates/bevy_ecs/src/system/query.rs | 20 +++++++++++++++++++ .../src/single_threaded_task_pool.rs | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 51b41a9c8e00c..179a063b1804d 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -1,3 +1,5 @@ +#[cfg(not(feature = "single-threaded"))] +use crate::schedule::ParallelExecutor; use crate::{ self as bevy_ecs, change_detection::CHECK_TICK_THRESHOLD, @@ -12,8 +14,6 @@ use crate::{ }, world::{World, WorldId}, }; -#[cfg(not(feature = "single-threaded"))] -use crate::schedule::ParallelExecutor; use bevy_ecs_macros::Resource; use bevy_utils::{tracing::warn, HashMap, HashSet}; use core::fmt::Debug; diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 8f7e4f70bc303..622ff42d34422 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -725,6 +725,15 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { }; } + #[cfg(feature = "single-threaded")] + pub fn par_for_each<'this>( + &'this self, + _batch_size: usize, + f: impl Fn(ROQueryItem<'this, Q>) + Send + Sync + Clone, + ) { + self.for_each(f); + } + /// Runs `f` on each read-only query item in parallel. /// /// Parallelization is achieved by using the [`World`]'s [`ComputeTaskPool`]. @@ -750,6 +759,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// /// - [`par_for_each_mut`](Self::par_for_each_mut) for operating on mutable query items. #[inline] + #[cfg(not(feature = "single-threaded"))] pub fn par_for_each<'this>( &'this self, batch_size: usize, @@ -768,6 +778,15 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { }; } + #[cfg(feature = "single-threaded")] + pub fn par_for_each_mut<'a>( + &'a mut self, + _batch_size: usize, + f: impl Fn(Q::Item<'a>) + Send + Sync + Clone, + ) { + self.for_each_mut(f); + } + /// Runs `f` on each read-only query item in parallel. /// /// Parallelization is achieved by using the [`World`]'s [`ComputeTaskPool`]. @@ -783,6 +802,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// /// - [`par_for_each`](Self::par_for_each) for more usage details. #[inline] + #[cfg(not(feature = "single-threaded"))] pub fn par_for_each_mut<'a>( &'a mut self, batch_size: usize, diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index 5760257f2e257..13251e90fa8ca 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -108,7 +108,7 @@ impl TaskPool { where T: 'static, { - LOCAL_EXECUTOR.with(|executor|{ + LOCAL_EXECUTOR.with(|executor| { let _task = executor.spawn(future); // Loop until all tasks are done while executor.try_tick() {} From 8f2bddc7c4e4de2bc09e7647fc6d22d4fef7d109 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 18 Nov 2022 20:02:30 -0800 Subject: [PATCH 05/17] Use single threaded alternatives for single threaded executors --- .../src/single_threaded_task_pool.rs | 24 ++++++++----------- .../src/wasm_single_threaded_task_pool.rs | 18 +++++++------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index 13251e90fa8ca..ddd70c8dd60ff 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -1,9 +1,4 @@ -use std::{ - future::Future, - marker::PhantomData, - mem, - sync::{Arc, Mutex}, -}; +use std::{cell::RefCell, future::Future, marker::PhantomData, mem, rc::Rc}; thread_local! { static LOCAL_EXECUTOR: async_executor::LocalExecutor<'static> = async_executor::LocalExecutor::new(); @@ -75,8 +70,9 @@ impl TaskPool { let executor: &'env async_executor::LocalExecutor<'env> = unsafe { mem::transmute(executor) }; - let results: Mutex>>>> = Mutex::new(Vec::new()); - let results: &'env Mutex>>>> = unsafe { mem::transmute(&results) }; + let results: RefCell>>>> = RefCell::new(Vec::new()); + let results: &'env RefCell>>>> = + unsafe { mem::transmute(&results) }; let mut scope = Scope { executor, @@ -92,10 +88,10 @@ impl TaskPool { // Loop until all tasks are done while executor.try_tick() {} - let results = scope.results.lock().unwrap(); + let results = scope.results.borrow(); results .iter() - .map(|result| result.lock().unwrap().take().unwrap()) + .map(|result| result.borrow_mut().take().unwrap()) .collect() } @@ -159,7 +155,7 @@ impl FakeTask { pub struct Scope<'scope, 'env: 'scope, T> { executor: &'env async_executor::LocalExecutor<'env>, // Vector to gather results of all futures spawned during scope run - results: &'env Mutex>>>>, + results: &'env RefCell>>>>, // make `Scope` invariant over 'scope and 'env scope: PhantomData<&'scope mut &'scope ()>, @@ -184,10 +180,10 @@ impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> { /// /// For more information, see [`TaskPool::scope`]. pub fn spawn_on_scope + 'env>(&self, f: Fut) { - let result = Arc::new(Mutex::new(None)); - self.results.lock().unwrap().push(result.clone()); + let result = Rc::new(RefCell::new(None)); + self.results.borrow_mut().push(result.clone()); let f = async move { - result.lock().unwrap().replace(f.await); + result.borrow_mut().replace(f.await); }; self.executor.spawn(f).detach(); } diff --git a/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs b/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs index 8fa37f4f2361b..0858f42314803 100644 --- a/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs @@ -2,7 +2,8 @@ use std::{ future::Future, marker::PhantomData, mem, - sync::{Arc, Mutex}, + rc::Rc, + cell::RefCell, }; /// Used to create a TaskPool @@ -71,8 +72,9 @@ impl TaskPool { let executor: &'env async_executor::LocalExecutor<'env> = unsafe { mem::transmute(executor) }; - let results: Mutex>>>> = Mutex::new(Vec::new()); - let results: &'env Mutex>>>> = unsafe { mem::transmute(&results) }; + let results: RefCell>>>> = RefCell::new(Vec::new()); + let results: &'env RefCell>>>> = unsafe { mem::transmute(&results) }; + let mut scope = Scope { executor, @@ -88,10 +90,10 @@ impl TaskPool { // Loop until all tasks are done while executor.try_tick() {} - let results = scope.results.lock().unwrap(); + let results = scope.results.borrow(); results .iter() - .map(|result| result.lock().unwrap().take().unwrap()) + .map(|result| result.borrow_mut().take().unwrap()) .collect() } @@ -138,7 +140,7 @@ impl FakeTask { pub struct Scope<'scope, 'env: 'scope, T> { executor: &'env async_executor::LocalExecutor<'env>, // Vector to gather results of all futures spawned during scope run - results: &'env Mutex>>>>, + results: &'env RefCell>>>>, // make `Scope` invariant over 'scope and 'env scope: PhantomData<&'scope mut &'scope ()>, @@ -164,9 +166,9 @@ impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> { /// For more information, see [`TaskPool::scope`]. pub fn spawn_on_scope + 'env>(&self, f: Fut) { let result = Arc::new(Mutex::new(None)); - self.results.lock().unwrap().push(result.clone()); + self.results.borrow_mut().push(result.clone()); let f = async move { - result.lock().unwrap().replace(f.await); + result.borrow_mut().replace(f.await); }; self.executor.spawn(f).detach(); } From 7c97966210db4f4c07179cc4e284974d5d23bd34 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 19 Nov 2022 03:44:30 -0800 Subject: [PATCH 06/17] Merge wasm back into single_threaded_task_pool --- crates/bevy_tasks/src/lib.rs | 9 +- .../src/single_threaded_task_pool.rs | 19 +- .../src/wasm_single_threaded_task_pool.rs | 175 ------------------ 3 files changed, 16 insertions(+), 187 deletions(-) delete mode 100644 crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index d86d188c2def3..9959c589d2915 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -12,16 +12,11 @@ mod task_pool; #[cfg(all(not(target_arch = "wasm32"), not(feature = "single-threaded")))] pub use task_pool::{Scope, TaskPool, TaskPoolBuilder}; -#[cfg(all(not(target_arch = "wasm32"), feature = "single-threaded"))] +#[cfg(any(not(target_arch = "wasm32"), feature = "single-threaded"))] mod single_threaded_task_pool; -#[cfg(all(not(target_arch = "wasm32"), feature = "single-threaded"))] +#[cfg(any(not(target_arch = "wasm32"), feature = "single-threaded"))] pub use single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder}; -#[cfg(target_arch = "wasm32")] -mod single_threaded_task_pool; -#[cfg(target_arch = "wasm32")] -pub use wasm_single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder}; - mod usages; #[cfg(not(target_arch = "wasm32"))] pub use usages::tick_global_task_pools_on_main_thread; diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index ddd70c8dd60ff..de98fa2311ea2 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, future::Future, marker::PhantomData, mem, rc::Rc}; thread_local! { + #[cfg(not(target_arch = "wasm32"))] static LOCAL_EXECUTOR: async_executor::LocalExecutor<'static> = async_executor::LocalExecutor::new(); } @@ -56,7 +57,7 @@ impl TaskPool { 1 } - /// Allows spawning non-`static futures on the thread pool. The function takes a callback, + /// Allows spawning non-'static futures on the thread pool. The function takes a callback, /// passing a scope object into it. The scope object provided to the callback can be used /// to spawn tasks. This function will await the completion of all tasks before returning. /// @@ -104,12 +105,20 @@ impl TaskPool { where T: 'static, { - LOCAL_EXECUTOR.with(|executor| { - let _task = executor.spawn(future); - // Loop until all tasks are done - while executor.try_tick() {} + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { + future.await; }); + #[cfg(not(target_arch = "wasm32"))] + { + LOCAL_EXECUTOR.with(|executor| { + let _task = executor.spawn(future); + // Loop until all tasks are done + while executor.try_tick() {} + }); + } + FakeTask } diff --git a/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs b/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs deleted file mode 100644 index 0858f42314803..0000000000000 --- a/crates/bevy_tasks/src/wasm_single_threaded_task_pool.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::{ - future::Future, - marker::PhantomData, - mem, - rc::Rc, - cell::RefCell, -}; - -/// Used to create a TaskPool -#[derive(Debug, Default, Clone)] -pub struct TaskPoolBuilder {} - -impl TaskPoolBuilder { - /// Creates a new TaskPoolBuilder instance - pub fn new() -> Self { - Self::default() - } - - /// No op on the single threaded task pool - pub fn num_threads(self, _num_threads: usize) -> Self { - self - } - - /// No op on the single threaded task pool - pub fn stack_size(self, _stack_size: usize) -> Self { - self - } - - /// No op on the single threaded task pool - pub fn thread_name(self, _thread_name: String) -> Self { - self - } - - /// Creates a new [`TaskPool`] - pub fn build(self) -> TaskPool { - TaskPool::new_internal() - } -} - -/// A thread pool for executing tasks. Tasks are futures that are being automatically driven by -/// the pool on threads owned by the pool. In this case - main thread only. -#[derive(Debug, Default, Clone)] -pub struct TaskPool {} - -impl TaskPool { - /// Create a `TaskPool` with the default configuration. - pub fn new() -> Self { - TaskPoolBuilder::new().build() - } - - #[allow(unused_variables)] - fn new_internal() -> Self { - Self {} - } - - /// Return the number of threads owned by the task pool - pub fn thread_num(&self) -> usize { - 1 - } - - /// Allows spawning non-`static futures on the thread pool. The function takes a callback, - /// passing a scope object into it. The scope object provided to the callback can be used - /// to spawn tasks. This function will await the completion of all tasks before returning. - /// - /// This is similar to `rayon::scope` and `crossbeam::scope` - pub fn scope<'env, F, T>(&self, f: F) -> Vec - where - F: for<'scope> FnOnce(&'env mut Scope<'scope, 'env, T>), - T: Send + 'static, - { - let executor = &async_executor::LocalExecutor::new(); - let executor: &'env async_executor::LocalExecutor<'env> = - unsafe { mem::transmute(executor) }; - - let results: RefCell>>>> = RefCell::new(Vec::new()); - let results: &'env RefCell>>>> = unsafe { mem::transmute(&results) }; - - - let mut scope = Scope { - executor, - results, - scope: PhantomData, - env: PhantomData, - }; - - let scope_ref: &'env mut Scope<'_, 'env, T> = unsafe { mem::transmute(&mut scope) }; - - f(scope_ref); - - // Loop until all tasks are done - while executor.try_tick() {} - - let results = scope.results.borrow(); - results - .iter() - .map(|result| result.borrow_mut().take().unwrap()) - .collect() - } - - /// Spawns a static future onto the JS event loop. For now it is returning FakeTask - /// instance with no-op detach method. Returning real Task is possible here, but tricky: - /// future is running on JS event loop, Task is running on async_executor::LocalExecutor - /// so some proxy future is needed. Moreover currently we don't have long-living - /// LocalExecutor here (above `spawn` implementation creates temporary one) - /// But for typical use cases it seems that current implementation should be sufficient: - /// caller can spawn long-running future writing results to some channel / event queue - /// and simply call detach on returned Task (like AssetServer does) - spawned future - /// can write results to some channel / event queue. - pub fn spawn(&self, future: impl Future + 'static) -> FakeTask - where - T: 'static, - { - wasm_bindgen_futures::spawn_local(async move { - future.await; - }); - FakeTask - } - - /// Spawns a static future on the JS event loop. This is exactly the same as [`TaskSpool::spawn`]. - pub fn spawn_local(&self, future: impl Future + 'static) -> FakeTask - where - T: 'static, - { - self.spawn(future) - } -} - -#[derive(Debug)] -pub struct FakeTask; - -impl FakeTask { - /// No op on the single threaded task pool - pub fn detach(self) {} -} - -/// A `TaskPool` scope for running one or more non-`'static` futures. -/// -/// For more information, see [`TaskPool::scope`]. -#[derive(Debug)] -pub struct Scope<'scope, 'env: 'scope, T> { - executor: &'env async_executor::LocalExecutor<'env>, - // Vector to gather results of all futures spawned during scope run - results: &'env RefCell>>>>, - - // make `Scope` invariant over 'scope and 'env - scope: PhantomData<&'scope mut &'scope ()>, - env: PhantomData<&'env mut &'env ()>, -} - -impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> { - /// Spawns a scoped future onto the thread-local executor. The scope *must* outlive - /// the provided future. The results of the future will be returned as a part of - /// [`TaskPool::scope`]'s return value. - /// - /// On the single threaded task pool, it just calls [`Scope::spawn_local`]. - /// - /// For more information, see [`TaskPool::scope`]. - pub fn spawn + 'env>(&self, f: Fut) { - self.spawn_on_scope(f); - } - - /// Spawns a scoped future that runs on the thread the scope called from. The - /// scope *must* outlive the provided future. The results of the future will be - /// returned as a part of [`TaskPool::scope`]'s return value. - /// - /// For more information, see [`TaskPool::scope`]. - pub fn spawn_on_scope + 'env>(&self, f: Fut) { - let result = Arc::new(Mutex::new(None)); - self.results.borrow_mut().push(result.clone()); - let f = async move { - result.borrow_mut().replace(f.await); - }; - self.executor.spawn(f).detach(); - } -} From 55bd888671efd4399f61d32a5c81260790a4b76b Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 30 Nov 2022 20:32:52 -0800 Subject: [PATCH 07/17] Invert feature --- Cargo.toml | 5 +++-- crates/bevy_ecs/Cargo.toml | 4 ++-- crates/bevy_ecs/src/query/state.rs | 8 ++++---- crates/bevy_internal/Cargo.toml | 2 +- crates/bevy_tasks/Cargo.toml | 4 ++-- crates/bevy_tasks/src/lib.rs | 8 ++++---- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 033ac20ec0dda..10534c74fb068 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ default = [ "bevy_scene", "bevy_winit", "render", + "multi-threaded", "png", "hdr", "vorbis", @@ -99,8 +100,8 @@ filesystem_watcher = ["bevy_internal/filesystem_watcher"] serialize = ["bevy_internal/serialize"] -# Disables all parallelism in the engine. Forces all engine tasks to run on a single thread. -single-threaded = ["bevy_internal/single-threaded"] +# Disabling this feature disables all parallelism in the engine. Forces all engine tasks to run on a single thread. +multi-threaded = ["bevy_internal/multi-threaded"] # Display server protocol support (X11 is enabled by default) wayland = ["bevy_internal/wayland"] diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index dded9c92cf0e4..d64d3e3734f27 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -11,8 +11,8 @@ categories = ["game-engines", "data-structures"] [features] trace = [] -single-threaded = [] -default = ["bevy_reflect"] +multi-threaded = ["bevy_tasks/multi-threaded"] +default = ["bevy_reflect", "multi-threaded"] [dependencies] bevy_ptr = { path = "../bevy_ptr", version = "0.9.0" } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index c10ef1fd39fcc..d658ef99b212e 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -834,7 +834,7 @@ impl QueryState { } #[inline] - #[cfg(feature = "single-threaded")] + #[cfg(any(target = "wasm32", not(feature = "multi-threaded")))] pub fn par_for_each<'w, FN: Fn(ROQueryItem<'w, Q>) + Send + Sync + Clone>( &mut self, world: &'w World, @@ -845,7 +845,7 @@ impl QueryState { } #[inline] - #[cfg(feature = "single-threaded")] + #[cfg(any(target = "wasm32", not(feature = "multi-threaded")))] pub fn par_for_each_mut<'w, FN: Fn(Q::Item<'w>) + Send + Sync + Clone>( &mut self, world: &'w mut World, @@ -864,7 +864,7 @@ impl QueryState { /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being /// initialized and run from the ECS scheduler, this should never panic. #[inline] - #[cfg(not(feature = "single-threaded"))] + #[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))] pub fn par_for_each<'w, FN: Fn(ROQueryItem<'w, Q>) + Send + Sync + Clone>( &mut self, world: &'w World, @@ -890,7 +890,7 @@ impl QueryState { /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being /// initialized and run from the ECS scheduler, this should never panic. #[inline] - #[cfg(not(feature = "single-threaded"))] + #[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))] pub fn par_for_each_mut<'w, FN: Fn(Q::Item<'w>) + Send + Sync + Clone>( &mut self, world: &'w mut World, diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 4d014c39ede93..1e9b168966803 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -47,7 +47,7 @@ wav = ["bevy_audio/wav"] filesystem_watcher = ["bevy_asset/filesystem_watcher"] serialize = ["bevy_core/serialize", "bevy_input/serialize", "bevy_time/serialize", "bevy_window/serialize", "bevy_transform/serialize", "bevy_math/serialize", "bevy_scene/serialize"] -single-threaded = ["bevy_ecs/single-threaded", "bevy_tasks/single-threaded"] +multi-threaded = ["bevy_ecs/multi-threaded", "bevy_tasks/multi-threaded"] # Display server protocol support (X11 is enabled by default) wayland = ["bevy_winit/wayland"] diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index e7a7c628e83c1..3d04173236e43 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -9,8 +9,8 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -single-threaded = [] -default = [] +multi-threaded = [] +default = ["multi-threaded"] [dependencies] futures-lite = "1.4.0" diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index 9959c589d2915..6d059a0ef4a46 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -7,14 +7,14 @@ pub use slice::{ParallelSlice, ParallelSliceMut}; mod task; pub use task::Task; -#[cfg(all(not(target_arch = "wasm32"), not(feature = "single-threaded")))] +#[cfg(all(not(target_arch = "wasm32"), feature = "multi-threaded"))] mod task_pool; -#[cfg(all(not(target_arch = "wasm32"), not(feature = "single-threaded")))] +#[cfg(all(not(target_arch = "wasm32"), feature = "multi-threaded"))] pub use task_pool::{Scope, TaskPool, TaskPoolBuilder}; -#[cfg(any(not(target_arch = "wasm32"), feature = "single-threaded"))] +#[cfg(any(target_arch = "wasm32", not(feature = "multi-threaded")))] mod single_threaded_task_pool; -#[cfg(any(not(target_arch = "wasm32"), feature = "single-threaded"))] +#[cfg(any(target_arch = "wasm32", not(feature = "multi-threaded")))] pub use single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder}; mod usages; From 9da076793041ae3e19596f0f383dbb50ee7ceaca Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 9 Apr 2023 13:41:13 -0700 Subject: [PATCH 08/17] Default to SingleThreadedExecutor when the feature is disabled --- crates/bevy_ecs/src/query/par_iter.rs | 8 ++++++-- crates/bevy_ecs/src/schedule/executor/mod.rs | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index aabc0f220fe57..2bf38e3eba288 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -157,8 +157,12 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { { let thread_count = ComputeTaskPool::get().thread_num(); if thread_count <= 1 { - self.state - .for_each_unchecked_manual(self.world, func, self.last_run, self.this_run); + self.state.for_each_unchecked_manual( + self.world, + func, + self.last_run, + self.this_run, + ); } else { // Need a batch size of at least 1. let batch_size = self.get_batch_size(thread_count).max(1); diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index 5de07a52f5493..7cb66b277dc51 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -33,13 +33,13 @@ pub enum ExecutorKind { /// /// Useful if you're dealing with a single-threaded environment, saving your threads for /// other things, or just trying minimize overhead. - #[cfg_attr(target_arch = "wasm32", default)] + #[cfg_attr(any(target_arch = "wasm32", not(feature = "multi-threaded")), default)] SingleThreaded, /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_buffers`](crate::system::System::apply_buffers) /// immediately after running each system. Simple, /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. - #[cfg_attr(not(target_arch = "wasm32"), default)] + #[cfg_attr(all(not(target_arch = "wasm32"), feature = "multi-threaded"), default)] MultiThreaded, } From e687186e40ad892ac2d90c7cd8241fd2e2fb3539 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 9 Apr 2023 13:44:07 -0700 Subject: [PATCH 09/17] Disable pipelined rendering if not multihtreaded --- crates/bevy_internal/src/default_plugins.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index fe3be50bf9536..95bee297d8188 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -81,7 +81,7 @@ impl PluginGroup for DefaultPlugins { // compressed texture formats .add(bevy_render::texture::ImagePlugin::default()); - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "multi-threaded"))] { group = group .add(bevy_render::pipelined_rendering::PipelinedRenderingPlugin::default()); From 29abb5350176510d222067d8be530f653d6e559e Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 4 Jul 2023 16:53:35 -0700 Subject: [PATCH 10/17] Fix CI --- crates/bevy_tasks/src/single_threaded_task_pool.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index da9b79c48abd4..3cc4fffc0b1e8 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -1,7 +1,6 @@ use std::{cell::RefCell, future::Future, marker::PhantomData, mem, rc::Rc}; thread_local! { - #[cfg(not(target_arch = "wasm32"))] static LOCAL_EXECUTOR: async_executor::LocalExecutor<'static> = async_executor::LocalExecutor::new(); } @@ -57,8 +56,8 @@ pub struct TaskPool {} impl TaskPool { /// Just create a new `ThreadExecutor` for wasm - pub fn get_thread_executor() -> Arc> { - Arc::new(ThreadExecutor::new()) + pub fn get_thread_executor() -> Rc> { + Rc::new(ThreadExecutor::new()) } /// Create a `TaskPool` with the default configuration. From 1db5640ce56e7744077904ab61e94c48d809e1c7 Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 4 Jul 2023 18:00:25 -0700 Subject: [PATCH 11/17] Fix WASM... again --- crates/bevy_tasks/src/single_threaded_task_pool.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index 3cc4fffc0b1e8..26970b5541d0c 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -1,4 +1,6 @@ use std::{cell::RefCell, future::Future, marker::PhantomData, mem, rc::Rc}; +#[cfg(target_arch = "wasm32")] +use std::sync::Arc; thread_local! { static LOCAL_EXECUTOR: async_executor::LocalExecutor<'static> = async_executor::LocalExecutor::new(); @@ -56,8 +58,8 @@ pub struct TaskPool {} impl TaskPool { /// Just create a new `ThreadExecutor` for wasm - pub fn get_thread_executor() -> Rc> { - Rc::new(ThreadExecutor::new()) + pub fn get_thread_executor() -> Arc> { + Arc::new(ThreadExecutor::new()) } /// Create a `TaskPool` with the default configuration. From 124f899138383452eee6f1a33eacbfdb1cfe9c2c Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 4 Jul 2023 18:01:33 -0700 Subject: [PATCH 12/17] Run the suggested command --- docs/cargo_features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 9fa26224539b8..497fa403a332c 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -27,11 +27,11 @@ The default feature set enables most of the expected features of a game engine, |bevy_text|Provides text functionality| |bevy_ui|A custom ECS-driven UI framework| |bevy_winit|winit window and input backend| -|multi-threaded|Enables use of multithreading throughout the engine. Does nothing on WASM platforms.| |default_font|Include a default font, containing only ASCII characters, at the cost of a 20kB binary size increase| |filesystem_watcher|Enable watching file system for asset hot reload| |hdr|HDR image format support| |ktx2|KTX2 compressed texture support| +|multi-threaded|Enables multithreaded parallelism in the engine. Disabling it forces all engine tasks to run on a single thread.| |png|PNG image format support| |tonemapping_luts|Include tonemapping Look Up Tables KTX2 files| |vorbis|OGG/VORBIS audio format support| From daded5b1937cb20959584d8652e0c41e3fb5228e Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 4 Jul 2023 18:54:10 -0700 Subject: [PATCH 13/17] Formatting --- crates/bevy_tasks/src/single_threaded_task_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index 26970b5541d0c..92e2928124c94 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -1,6 +1,6 @@ -use std::{cell::RefCell, future::Future, marker::PhantomData, mem, rc::Rc}; #[cfg(target_arch = "wasm32")] use std::sync::Arc; +use std::{cell::RefCell, future::Future, marker::PhantomData, mem, rc::Rc}; thread_local! { static LOCAL_EXECUTOR: async_executor::LocalExecutor<'static> = async_executor::LocalExecutor::new(); From ba64944898e86f133aaff25792eb012ae81c2954 Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 4 Jul 2023 22:27:17 -0700 Subject: [PATCH 14/17] Run the command --- docs/cargo_features.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 497fa403a332c..7fed56a4c3fbc 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -31,7 +31,8 @@ The default feature set enables most of the expected features of a game engine, |filesystem_watcher|Enable watching file system for asset hot reload| |hdr|HDR image format support| |ktx2|KTX2 compressed texture support| -|multi-threaded|Enables multithreaded parallelism in the engine. Disabling it forces all engine tasks to run on a single thread.| +|multi-threaded|Enables multithreaded parallelism in the engine. Disabling it forces all engine +# tasks to run on a single thread.| |png|PNG image format support| |tonemapping_luts|Include tonemapping Look Up Tables KTX2 files| |vorbis|OGG/VORBIS audio format support| From ad582ae6fbf3c8276c807faeb819005690aac62c Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 5 Jul 2023 04:02:52 -0700 Subject: [PATCH 15/17] Do as mockersf suggests --- Cargo.toml | 3 +-- docs/cargo_features.md | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0a88b5e7cd98b..3f3f3fdc24fc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -200,8 +200,7 @@ filesystem_watcher = ["bevy_internal/filesystem_watcher"] # Enable serialization support through serde serialize = ["bevy_internal/serialize"] -# Enables multithreaded parallelism in the engine. Disabling it forces all engine -# tasks to run on a single thread. +# Enables multithreaded parallelism in the engine. Disabling it forces all engine tasks to run on a single thread. multi-threaded = ["bevy_internal/multi-threaded"] # Wayland display server support diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 7fed56a4c3fbc..497fa403a332c 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -31,8 +31,7 @@ The default feature set enables most of the expected features of a game engine, |filesystem_watcher|Enable watching file system for asset hot reload| |hdr|HDR image format support| |ktx2|KTX2 compressed texture support| -|multi-threaded|Enables multithreaded parallelism in the engine. Disabling it forces all engine -# tasks to run on a single thread.| +|multi-threaded|Enables multithreaded parallelism in the engine. Disabling it forces all engine tasks to run on a single thread.| |png|PNG image format support| |tonemapping_luts|Include tonemapping Look Up Tables KTX2 files| |vorbis|OGG/VORBIS audio format support| From bc3d25849ba3bbe0d7bf84ca76cb975fd38ca9c7 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 7 Jul 2023 17:22:00 -0700 Subject: [PATCH 16/17] Resolve warnings when building without the multi-threaded feature --- crates/bevy_ecs/src/query/par_iter.rs | 6 ++++-- crates/bevy_ecs/src/query/state.rs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index 24a077191cc9c..6df124848adde 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -1,5 +1,4 @@ use crate::{component::Tick, world::unsafe_world_cell::UnsafeWorldCell}; -use bevy_tasks::ComputeTaskPool; use std::ops::Range; use super::{QueryItem, QueryState, ROQueryItem, ReadOnlyWorldQuery, WorldQuery}; @@ -34,6 +33,8 @@ pub struct BatchingStrategy { /// increase the scheduling overhead for the iteration. /// /// Defaults to 1. + /// + /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool pub batches_per_thread: usize, } @@ -155,7 +156,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { } #[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))] { - let thread_count = ComputeTaskPool::get().thread_num(); + let thread_count = bevy_tasks::ComputeTaskPool::get().thread_num(); if thread_count <= 1 { self.state.for_each_unchecked_manual( self.world, @@ -177,6 +178,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { } } + #[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))] fn get_batch_size(&self, thread_count: usize) -> usize { if self.batching_strategy.batch_size_limits.is_empty() { return self.batching_strategy.batch_size_limits.start; diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index aee797ccf662a..24e368f36e8a5 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -10,7 +10,6 @@ use crate::{ storage::{TableId, TableRow}, world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, }; -use bevy_tasks::ComputeTaskPool; #[cfg(feature = "trace")] use bevy_utils::tracing::Instrument; use fixedbitset::FixedBitSet; @@ -1031,6 +1030,7 @@ impl QueryState { /// have unique access to the components they query. /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` /// with a mismatched [`WorldId`] is unsound. + #[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))] pub(crate) unsafe fn par_for_each_unchecked_manual< 'w, FN: Fn(Q::Item<'w>) + Send + Sync + Clone, @@ -1044,7 +1044,7 @@ impl QueryState { ) { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual - ComputeTaskPool::get().scope(|scope| { + bevy_tasks::ComputeTaskPool::get().scope(|scope| { if Q::IS_DENSE && F::IS_DENSE { // SAFETY: We only access table data that has been registered in `self.archetype_component_access`. let tables = &world.storages().tables; From d40d3efd5e7e5cca52ccb593876d640e6e77fe13 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Sat, 8 Jul 2023 21:03:14 -0700 Subject: [PATCH 17/17] Fix doc --- crates/bevy_ecs/src/query/state.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 24e368f36e8a5..ef01805937ad8 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1030,6 +1030,8 @@ impl QueryState { /// have unique access to the components they query. /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` /// with a mismatched [`WorldId`] is unsound. + /// + /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[cfg(all(not(target = "wasm32"), feature = "multi-threaded"))] pub(crate) unsafe fn par_for_each_unchecked_manual< 'w,