Add no_std
support to bevy_tasks
(#15464)
# Objective - Contributes to #15460 ## Solution - Added the following features: - `std` (default) - `async_executor` (default) - `edge_executor` - `critical-section` - `portable-atomic` - Added [`edge-executor`](https://crates.io/crates/edge-executor) as a `no_std` alternative to `async-executor`. - Updated the `single_threaded_task_pool` to work in `no_std` environments by gating its reliance on `thread_local`. ## Testing - Added to `compile-check-no-std` CI command ## Notes - In previous iterations of this PR, a custom `async-executor` alternative was vendored in. This raised concerns around maintenance and testing. In this iteration, an existing version of that same vendoring is now used, but _only_ in `no_std` contexts. For existing `std` contexts, the original `async-executor` is used. - Due to the way statics work, certain `TaskPool` operations have added restrictions around `Send`/`Sync` in `no_std`. This is because there isn't a straightforward way to create a thread-local in `no_std`. If these added constraints pose an issue we can revisit this at a later date. - If a user enables both the `async_executor` and `edge_executor` features, we will default to using `async-executor`. Since enabling `async_executor` requires `std`, we can safely assume we are in an `std` context and use the original library. --------- Co-authored-by: Mike <2180432+hymm@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
bc572cd270
commit
72f096c91e
@ -9,14 +9,57 @@ license = "MIT OR Apache-2.0"
|
|||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
multi_threaded = ["dep:async-channel", "dep:concurrent-queue"]
|
default = ["std", "async_executor"]
|
||||||
|
std = [
|
||||||
|
"futures-lite/std",
|
||||||
|
"async-task/std",
|
||||||
|
"spin/std",
|
||||||
|
"edge-executor?/std",
|
||||||
|
"portable-atomic-util?/std",
|
||||||
|
]
|
||||||
|
multi_threaded = ["std", "dep:async-channel", "dep:concurrent-queue"]
|
||||||
|
async_executor = ["std", "dep:async-executor"]
|
||||||
|
edge_executor = ["dep:edge-executor"]
|
||||||
|
critical-section = [
|
||||||
|
"dep:critical-section",
|
||||||
|
"edge-executor?/critical-section",
|
||||||
|
"portable-atomic?/critical-section",
|
||||||
|
]
|
||||||
|
portable-atomic = [
|
||||||
|
"dep:portable-atomic",
|
||||||
|
"dep:portable-atomic-util",
|
||||||
|
"edge-executor?/portable-atomic",
|
||||||
|
"async-task/portable-atomic",
|
||||||
|
"spin/portable_atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures-lite = "2.0.1"
|
futures-lite = { version = "2.0.1", default-features = false, features = [
|
||||||
async-executor = "1.11"
|
"alloc",
|
||||||
|
] }
|
||||||
|
async-task = { version = "4.4.0", default-features = false }
|
||||||
|
spin = { version = "0.9.8", default-features = false, features = [
|
||||||
|
"spin_mutex",
|
||||||
|
"rwlock",
|
||||||
|
"once",
|
||||||
|
] }
|
||||||
|
derive_more = { version = "1", default-features = false, features = [
|
||||||
|
"deref",
|
||||||
|
"deref_mut",
|
||||||
|
] }
|
||||||
|
|
||||||
|
async-executor = { version = "1.11", optional = true }
|
||||||
|
edge-executor = { version = "0.4.1", default-features = false, optional = true }
|
||||||
async-channel = { version = "2.3.0", optional = true }
|
async-channel = { version = "2.3.0", optional = true }
|
||||||
async-io = { version = "2.0.0", optional = true }
|
async-io = { version = "2.0.0", optional = true }
|
||||||
concurrent-queue = { version = "2.0.0", optional = true }
|
concurrent-queue = { version = "2.0.0", optional = true }
|
||||||
|
critical-section = { version = "1.2.0", optional = true }
|
||||||
|
portable-atomic = { version = "1", default-features = false, features = [
|
||||||
|
"fallback",
|
||||||
|
], optional = true }
|
||||||
|
portable-atomic-util = { version = "0.2.4", features = [
|
||||||
|
"alloc",
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
@ -34,6 +34,10 @@ The determining factor for what kind of work should go in each pool is latency r
|
|||||||
await receiving data from somewhere (i.e. disk) and signal other systems when the data is ready
|
await receiving data from somewhere (i.e. disk) and signal other systems when the data is ready
|
||||||
for consumption. (likely via channels)
|
for consumption. (likely via channels)
|
||||||
|
|
||||||
|
## `no_std` Support
|
||||||
|
|
||||||
|
To enable `no_std` support in this crate, you will need to disable default features, and enable the `edge_executor` and `critical-section` features. For platforms without full support for Rust atomics, you may also need to enable the `portable-atomic` feature.
|
||||||
|
|
||||||
[bevy]: https://bevyengine.org
|
[bevy]: https://bevyengine.org
|
||||||
[rayon]: https://github.com/rayon-rs/rayon
|
[rayon]: https://github.com/rayon-rs/rayon
|
||||||
[async-executor]: https://github.com/stjepang/async-executor
|
[async-executor]: https://github.com/stjepang/async-executor
|
||||||
|
84
crates/bevy_tasks/src/executor.rs
Normal file
84
crates/bevy_tasks/src/executor.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
//! Provides a fundamental executor primitive appropriate for the target platform
|
||||||
|
//! and feature set selected.
|
||||||
|
//! By default, the `async_executor` feature will be enabled, which will rely on
|
||||||
|
//! [`async-executor`] for the underlying implementation. This requires `std`,
|
||||||
|
//! so is not suitable for `no_std` contexts. Instead, you must use `edge_executor`,
|
||||||
|
//! which relies on the alternate [`edge-executor`] backend.
|
||||||
|
//!
|
||||||
|
//! [`async-executor`]: https://crates.io/crates/async-executor
|
||||||
|
//! [`edge-executor`]: https://crates.io/crates/edge-executor
|
||||||
|
|
||||||
|
pub use async_task::Task;
|
||||||
|
use core::{
|
||||||
|
fmt,
|
||||||
|
panic::{RefUnwindSafe, UnwindSafe},
|
||||||
|
};
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
|
||||||
|
#[cfg(feature = "multi_threaded")]
|
||||||
|
pub use async_task::FallibleTask;
|
||||||
|
|
||||||
|
#[cfg(feature = "async_executor")]
|
||||||
|
type ExecutorInner<'a> = async_executor::Executor<'a>;
|
||||||
|
|
||||||
|
#[cfg(feature = "async_executor")]
|
||||||
|
type LocalExecutorInner<'a> = async_executor::LocalExecutor<'a>;
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "async_executor"), feature = "edge_executor"))]
|
||||||
|
type ExecutorInner<'a> = edge_executor::Executor<'a, 64>;
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "async_executor"), feature = "edge_executor"))]
|
||||||
|
type LocalExecutorInner<'a> = edge_executor::LocalExecutor<'a, 64>;
|
||||||
|
|
||||||
|
/// Wrapper around a multi-threading-aware async executor.
|
||||||
|
/// Spawning will generally require tasks to be `Send` and `Sync` to allow multiple
|
||||||
|
/// threads to send/receive/advance tasks.
|
||||||
|
///
|
||||||
|
/// If you require an executor _without_ the `Send` and `Sync` requirements, consider
|
||||||
|
/// using [`LocalExecutor`] instead.
|
||||||
|
#[derive(Deref, DerefMut, Default)]
|
||||||
|
pub struct Executor<'a>(ExecutorInner<'a>);
|
||||||
|
|
||||||
|
/// Wrapper around a single-threaded async executor.
|
||||||
|
/// Spawning wont generally require tasks to be `Send` and `Sync`, at the cost of
|
||||||
|
/// this executor itself not being `Send` or `Sync`. This makes it unsuitable for
|
||||||
|
/// global statics.
|
||||||
|
///
|
||||||
|
/// If need to store an executor in a global static, or send across threads,
|
||||||
|
/// consider using [`Executor`] instead.
|
||||||
|
#[derive(Deref, DerefMut, Default)]
|
||||||
|
pub struct LocalExecutor<'a>(LocalExecutorInner<'a>);
|
||||||
|
|
||||||
|
impl Executor<'_> {
|
||||||
|
/// Construct a new [`Executor`]
|
||||||
|
#[allow(dead_code, reason = "not all feature flags require this function")]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self(ExecutorInner::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalExecutor<'_> {
|
||||||
|
/// Construct a new [`LocalExecutor`]
|
||||||
|
#[allow(dead_code, reason = "not all feature flags require this function")]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self(LocalExecutorInner::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnwindSafe for Executor<'_> {}
|
||||||
|
impl RefUnwindSafe for Executor<'_> {}
|
||||||
|
|
||||||
|
impl UnwindSafe for LocalExecutor<'_> {}
|
||||||
|
impl RefUnwindSafe for LocalExecutor<'_> {}
|
||||||
|
|
||||||
|
impl fmt::Debug for Executor<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Executor").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for LocalExecutor<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("LocalExecutor").finish()
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
use crate::iter::ParallelIterator;
|
use crate::iter::ParallelIterator;
|
||||||
|
|
||||||
|
/// Chains two [`ParallelIterator`]s `T` and `U`, first returning
|
||||||
|
/// batches from `T`, and then from `U`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Chain<T, U> {
|
pub struct Chain<T, U> {
|
||||||
pub(crate) left: T,
|
pub(crate) left: T,
|
||||||
@ -24,6 +26,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maps a [`ParallelIterator`] `P` using the provided function `F`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Map<P, F> {
|
pub struct Map<P, F> {
|
||||||
pub(crate) iter: P,
|
pub(crate) iter: P,
|
||||||
@ -41,6 +44,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Filters a [`ParallelIterator`] `P` using the provided predicate `F`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Filter<P, F> {
|
pub struct Filter<P, F> {
|
||||||
pub(crate) iter: P,
|
pub(crate) iter: P,
|
||||||
@ -60,6 +64,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Filter-maps a [`ParallelIterator`] `P` using the provided function `F`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FilterMap<P, F> {
|
pub struct FilterMap<P, F> {
|
||||||
pub(crate) iter: P,
|
pub(crate) iter: P,
|
||||||
@ -77,6 +82,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flat-maps a [`ParallelIterator`] `P` using the provided function `F`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FlatMap<P, F> {
|
pub struct FlatMap<P, F> {
|
||||||
pub(crate) iter: P,
|
pub(crate) iter: P,
|
||||||
@ -98,6 +104,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flattens a [`ParallelIterator`] `P`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Flatten<P> {
|
pub struct Flatten<P> {
|
||||||
pub(crate) iter: P,
|
pub(crate) iter: P,
|
||||||
@ -117,6 +124,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fuses a [`ParallelIterator`] `P`, ensuring once it returns [`None`] once, it always
|
||||||
|
/// returns [`None`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Fuse<P> {
|
pub struct Fuse<P> {
|
||||||
pub(crate) iter: Option<P>,
|
pub(crate) iter: Option<P>,
|
||||||
@ -138,6 +147,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inspects a [`ParallelIterator`] `P` using the provided function `F`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Inspect<P, F> {
|
pub struct Inspect<P, F> {
|
||||||
pub(crate) iter: P,
|
pub(crate) iter: P,
|
||||||
@ -155,6 +165,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copies a [`ParallelIterator`] `P`'s returned values.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Copied<P> {
|
pub struct Copied<P> {
|
||||||
pub(crate) iter: P,
|
pub(crate) iter: P,
|
||||||
@ -171,6 +182,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clones a [`ParallelIterator`] `P`'s returned values.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Cloned<P> {
|
pub struct Cloned<P> {
|
||||||
pub(crate) iter: P,
|
pub(crate) iter: P,
|
||||||
@ -187,6 +199,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cycles a [`ParallelIterator`] `P` indefinitely.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Cycle<P> {
|
pub struct Cycle<P> {
|
||||||
pub(crate) iter: P,
|
pub(crate) iter: P,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::TaskPool;
|
use crate::TaskPool;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
mod adapters;
|
mod adapters;
|
||||||
pub use adapters::*;
|
pub use adapters::*;
|
||||||
|
@ -4,9 +4,12 @@
|
|||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
|
mod executor;
|
||||||
|
|
||||||
mod slice;
|
mod slice;
|
||||||
pub use slice::{ParallelSlice, ParallelSliceMut};
|
pub use slice::{ParallelSlice, ParallelSliceMut};
|
||||||
|
|
||||||
@ -37,9 +40,9 @@ mod thread_executor;
|
|||||||
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
|
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
|
||||||
pub use thread_executor::{ThreadExecutor, ThreadExecutorTicker};
|
pub use thread_executor::{ThreadExecutor, ThreadExecutorTicker};
|
||||||
|
|
||||||
#[cfg(feature = "async-io")]
|
#[cfg(all(feature = "async-io", feature = "std"))]
|
||||||
pub use async_io::block_on;
|
pub use async_io::block_on;
|
||||||
#[cfg(not(feature = "async-io"))]
|
#[cfg(all(not(feature = "async-io"), feature = "std"))]
|
||||||
pub use futures_lite::future::block_on;
|
pub use futures_lite::future::block_on;
|
||||||
pub use futures_lite::future::poll_once;
|
pub use futures_lite::future::poll_once;
|
||||||
|
|
||||||
@ -54,13 +57,17 @@ pub use futures_lite;
|
|||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
block_on,
|
|
||||||
iter::ParallelIterator,
|
iter::ParallelIterator,
|
||||||
slice::{ParallelSlice, ParallelSliceMut},
|
slice::{ParallelSlice, ParallelSliceMut},
|
||||||
usages::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool},
|
usages::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use crate::block_on;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
use core::num::NonZero;
|
use core::num::NonZero;
|
||||||
|
|
||||||
/// Gets the logical CPU core count available to the current process.
|
/// Gets the logical CPU core count available to the current process.
|
||||||
@ -69,8 +76,18 @@ use core::num::NonZero;
|
|||||||
/// it will return a default value of 1 if it internally errors out.
|
/// it will return a default value of 1 if it internally errors out.
|
||||||
///
|
///
|
||||||
/// This will always return at least 1.
|
/// This will always return at least 1.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
pub fn available_parallelism() -> usize {
|
pub fn available_parallelism() -> usize {
|
||||||
std::thread::available_parallelism()
|
std::thread::available_parallelism()
|
||||||
.map(NonZero::<usize>::get)
|
.map(NonZero::<usize>::get)
|
||||||
.unwrap_or(1)
|
.unwrap_or(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the logical CPU core count available to the current process.
|
||||||
|
///
|
||||||
|
/// This will always return at least 1.
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
pub fn available_parallelism() -> usize {
|
||||||
|
// Without access to std, assume a single thread is available
|
||||||
|
1
|
||||||
|
}
|
||||||
|
@ -1,12 +1,34 @@
|
|||||||
use alloc::{rc::Rc, sync::Arc};
|
use alloc::{string::String, vec::Vec};
|
||||||
use core::{cell::RefCell, future::Future, marker::PhantomData, mem};
|
use core::{cell::RefCell, future::Future, marker::PhantomData, mem};
|
||||||
|
|
||||||
use crate::Task;
|
use crate::Task;
|
||||||
|
|
||||||
|
#[cfg(feature = "portable-atomic")]
|
||||||
|
use portable_atomic_util::Arc;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "portable-atomic"))]
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use crate::executor::LocalExecutor;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
use crate::executor::Executor as LocalExecutor;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static LOCAL_EXECUTOR: async_executor::LocalExecutor<'static> = const { async_executor::LocalExecutor::new() };
|
static LOCAL_EXECUTOR: LocalExecutor<'static> = const { LocalExecutor::new() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
static LOCAL_EXECUTOR: LocalExecutor<'static> = const { LocalExecutor::new() };
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
type ScopeResult<T> = alloc::rc::Rc<RefCell<Option<T>>>;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
type ScopeResult<T> = Arc<spin::Mutex<Option<T>>>;
|
||||||
|
|
||||||
/// Used to create a [`TaskPool`].
|
/// Used to create a [`TaskPool`].
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct TaskPoolBuilder {}
|
pub struct TaskPoolBuilder {}
|
||||||
@ -124,15 +146,13 @@ impl TaskPool {
|
|||||||
// Any usages of the references passed into `Scope` must be accessed through
|
// Any usages of the references passed into `Scope` must be accessed through
|
||||||
// the transmuted reference for the rest of this function.
|
// the transmuted reference for the rest of this function.
|
||||||
|
|
||||||
let executor = &async_executor::LocalExecutor::new();
|
let executor = &LocalExecutor::new();
|
||||||
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
|
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
|
||||||
let executor: &'env async_executor::LocalExecutor<'env> =
|
let executor: &'env LocalExecutor<'env> = unsafe { mem::transmute(executor) };
|
||||||
unsafe { mem::transmute(executor) };
|
|
||||||
|
|
||||||
let results: RefCell<Vec<Rc<RefCell<Option<T>>>>> = RefCell::new(Vec::new());
|
let results: RefCell<Vec<ScopeResult<T>>> = RefCell::new(Vec::new());
|
||||||
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
|
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
|
||||||
let results: &'env RefCell<Vec<Rc<RefCell<Option<T>>>>> =
|
let results: &'env RefCell<Vec<ScopeResult<T>>> = unsafe { mem::transmute(&results) };
|
||||||
unsafe { mem::transmute(&results) };
|
|
||||||
|
|
||||||
let mut scope = Scope {
|
let mut scope = Scope {
|
||||||
executor,
|
executor,
|
||||||
@ -152,7 +172,16 @@ impl TaskPool {
|
|||||||
let results = scope.results.borrow();
|
let results = scope.results.borrow();
|
||||||
results
|
results
|
||||||
.iter()
|
.iter()
|
||||||
.map(|result| result.borrow_mut().take().unwrap())
|
.map(|result| {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
return result.borrow_mut().take().unwrap();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
{
|
||||||
|
let mut lock = result.lock();
|
||||||
|
lock.take().unwrap()
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,29 +191,42 @@ impl TaskPool {
|
|||||||
/// end-user.
|
/// 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<T>(&self, future: impl Future<Output = T> + 'static) -> Task<T>
|
pub fn spawn<T>(
|
||||||
|
&self,
|
||||||
|
future: impl Future<Output = T> + 'static + MaybeSend + MaybeSync,
|
||||||
|
) -> Task<T>
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static + MaybeSend + MaybeSync,
|
||||||
{
|
{
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(all(target_arch = "wasm32", feature = "std"))]
|
||||||
return Task::wrap_future(future);
|
return Task::wrap_future(future);
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
|
||||||
{
|
return LOCAL_EXECUTOR.with(|executor| {
|
||||||
LOCAL_EXECUTOR.with(|executor| {
|
|
||||||
let task = executor.spawn(future);
|
let task = executor.spawn(future);
|
||||||
// Loop until all tasks are done
|
// Loop until all tasks are done
|
||||||
while executor.try_tick() {}
|
while executor.try_tick() {}
|
||||||
|
|
||||||
Task::new(task)
|
Task::new(task)
|
||||||
})
|
});
|
||||||
}
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
return {
|
||||||
|
let task = LOCAL_EXECUTOR.spawn(future);
|
||||||
|
// Loop until all tasks are done
|
||||||
|
while LOCAL_EXECUTOR.try_tick() {}
|
||||||
|
|
||||||
|
Task::new(task)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a static future on the JS event loop. This is exactly the same as [`TaskPool::spawn`].
|
/// Spawns a static future on the JS event loop. This is exactly the same as [`TaskPool::spawn`].
|
||||||
pub fn spawn_local<T>(&self, future: impl Future<Output = T> + 'static) -> Task<T>
|
pub fn spawn_local<T>(
|
||||||
|
&self,
|
||||||
|
future: impl Future<Output = T> + 'static + MaybeSend + MaybeSync,
|
||||||
|
) -> Task<T>
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static + MaybeSend + MaybeSync,
|
||||||
{
|
{
|
||||||
self.spawn(future)
|
self.spawn(future)
|
||||||
}
|
}
|
||||||
@ -202,9 +244,13 @@ impl TaskPool {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn with_local_executor<F, R>(&self, f: F) -> R
|
pub fn with_local_executor<F, R>(&self, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&async_executor::LocalExecutor) -> R,
|
F: FnOnce(&LocalExecutor) -> R,
|
||||||
{
|
{
|
||||||
LOCAL_EXECUTOR.with(f)
|
#[cfg(feature = "std")]
|
||||||
|
return LOCAL_EXECUTOR.with(f);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
return f(&LOCAL_EXECUTOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,9 +259,9 @@ impl TaskPool {
|
|||||||
/// For more information, see [`TaskPool::scope`].
|
/// For more information, see [`TaskPool::scope`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scope<'scope, 'env: 'scope, T> {
|
pub struct Scope<'scope, 'env: 'scope, T> {
|
||||||
executor: &'scope async_executor::LocalExecutor<'scope>,
|
executor: &'scope LocalExecutor<'scope>,
|
||||||
// Vector to gather results of all futures spawned during scope run
|
// Vector to gather results of all futures spawned during scope run
|
||||||
results: &'env RefCell<Vec<Rc<RefCell<Option<T>>>>>,
|
results: &'env RefCell<Vec<ScopeResult<T>>>,
|
||||||
|
|
||||||
// make `Scope` invariant over 'scope and 'env
|
// make `Scope` invariant over 'scope and 'env
|
||||||
scope: PhantomData<&'scope mut &'scope ()>,
|
scope: PhantomData<&'scope mut &'scope ()>,
|
||||||
@ -230,7 +276,7 @@ impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> {
|
|||||||
/// On the single threaded task pool, it just calls [`Scope::spawn_on_scope`].
|
/// On the single threaded task pool, it just calls [`Scope::spawn_on_scope`].
|
||||||
///
|
///
|
||||||
/// For more information, see [`TaskPool::scope`].
|
/// For more information, see [`TaskPool::scope`].
|
||||||
pub fn spawn<Fut: Future<Output = T> + 'scope>(&self, f: Fut) {
|
pub fn spawn<Fut: Future<Output = T> + 'scope + MaybeSend>(&self, f: Fut) {
|
||||||
self.spawn_on_scope(f);
|
self.spawn_on_scope(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +287,7 @@ impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> {
|
|||||||
/// On the single threaded task pool, it just calls [`Scope::spawn_on_scope`].
|
/// On the single threaded task pool, it just calls [`Scope::spawn_on_scope`].
|
||||||
///
|
///
|
||||||
/// For more information, see [`TaskPool::scope`].
|
/// For more information, see [`TaskPool::scope`].
|
||||||
pub fn spawn_on_external<Fut: Future<Output = T> + 'scope>(&self, f: Fut) {
|
pub fn spawn_on_external<Fut: Future<Output = T> + 'scope + MaybeSend>(&self, f: Fut) {
|
||||||
self.spawn_on_scope(f);
|
self.spawn_on_scope(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,13 +296,41 @@ impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> {
|
|||||||
/// returned as a part of [`TaskPool::scope`]'s return value.
|
/// returned as a part of [`TaskPool::scope`]'s return value.
|
||||||
///
|
///
|
||||||
/// For more information, see [`TaskPool::scope`].
|
/// For more information, see [`TaskPool::scope`].
|
||||||
pub fn spawn_on_scope<Fut: Future<Output = T> + 'scope>(&self, f: Fut) {
|
pub fn spawn_on_scope<Fut: Future<Output = T> + 'scope + MaybeSend>(&self, f: Fut) {
|
||||||
let result = Rc::new(RefCell::new(None));
|
let result = ScopeResult::<T>::default();
|
||||||
self.results.borrow_mut().push(result.clone());
|
self.results.borrow_mut().push(result.clone());
|
||||||
let f = async move {
|
let f = async move {
|
||||||
let temp_result = f.await;
|
let temp_result = f.await;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
result.borrow_mut().replace(temp_result);
|
result.borrow_mut().replace(temp_result);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
{
|
||||||
|
let mut lock = result.lock();
|
||||||
|
*lock = Some(temp_result);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.executor.spawn(f).detach();
|
self.executor.spawn(f).detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
mod send_sync_bounds {
|
||||||
|
pub trait MaybeSend {}
|
||||||
|
impl<T> MaybeSend for T {}
|
||||||
|
|
||||||
|
pub trait MaybeSync {}
|
||||||
|
impl<T> MaybeSync for T {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
mod send_sync_bounds {
|
||||||
|
pub trait MaybeSend: Send {}
|
||||||
|
impl<T: Send> MaybeSend for T {}
|
||||||
|
|
||||||
|
pub trait MaybeSync: Sync {}
|
||||||
|
impl<T: Sync> MaybeSync for T {}
|
||||||
|
}
|
||||||
|
|
||||||
|
use send_sync_bounds::{MaybeSend, MaybeSync};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use super::TaskPool;
|
use super::TaskPool;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
/// Provides functions for mapping read-only slices across a provided [`TaskPool`].
|
/// Provides functions for mapping read-only slices across a provided [`TaskPool`].
|
||||||
pub trait ParallelSlice<T: Sync>: AsRef<[T]> {
|
pub trait ParallelSlice<T: Sync>: AsRef<[T]> {
|
||||||
|
@ -14,11 +14,11 @@ use core::{
|
|||||||
/// Tasks that panic get immediately canceled. Awaiting a canceled task also causes a panic.
|
/// Tasks that panic get immediately canceled. Awaiting a canceled task also causes a panic.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[must_use = "Tasks are canceled when dropped, use `.detach()` to run them in the background."]
|
#[must_use = "Tasks are canceled when dropped, use `.detach()` to run them in the background."]
|
||||||
pub struct Task<T>(async_executor::Task<T>);
|
pub struct Task<T>(crate::executor::Task<T>);
|
||||||
|
|
||||||
impl<T> Task<T> {
|
impl<T> Task<T> {
|
||||||
/// Creates a new task from a given `async_executor::Task`
|
/// Creates a new task from a given `async_executor::Task`
|
||||||
pub fn new(task: async_executor::Task<T>) -> Self {
|
pub fn new(task: crate::executor::Task<T>) -> Self {
|
||||||
Self(task)
|
Self(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use alloc::sync::Arc;
|
|||||||
use core::{future::Future, marker::PhantomData, mem, panic::AssertUnwindSafe};
|
use core::{future::Future, marker::PhantomData, mem, panic::AssertUnwindSafe};
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
|
|
||||||
use async_executor::FallibleTask;
|
use crate::executor::FallibleTask;
|
||||||
use concurrent_queue::ConcurrentQueue;
|
use concurrent_queue::ConcurrentQueue;
|
||||||
use futures_lite::FutureExt;
|
use futures_lite::FutureExt;
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ impl TaskPoolBuilder {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TaskPool {
|
pub struct TaskPool {
|
||||||
/// The executor for the pool.
|
/// The executor for the pool.
|
||||||
executor: Arc<async_executor::Executor<'static>>,
|
executor: Arc<crate::executor::Executor<'static>>,
|
||||||
|
|
||||||
// The inner state of the pool.
|
// The inner state of the pool.
|
||||||
threads: Vec<JoinHandle<()>>,
|
threads: Vec<JoinHandle<()>>,
|
||||||
@ -111,7 +111,7 @@ pub struct TaskPool {
|
|||||||
|
|
||||||
impl TaskPool {
|
impl TaskPool {
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static LOCAL_EXECUTOR: async_executor::LocalExecutor<'static> = const { async_executor::LocalExecutor::new() };
|
static LOCAL_EXECUTOR: crate::executor::LocalExecutor<'static> = const { crate::executor::LocalExecutor::new() };
|
||||||
static THREAD_EXECUTOR: Arc<ThreadExecutor<'static>> = Arc::new(ThreadExecutor::new());
|
static THREAD_EXECUTOR: Arc<ThreadExecutor<'static>> = Arc::new(ThreadExecutor::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ impl TaskPool {
|
|||||||
fn new_internal(builder: TaskPoolBuilder) -> Self {
|
fn new_internal(builder: TaskPoolBuilder) -> Self {
|
||||||
let (shutdown_tx, shutdown_rx) = async_channel::unbounded::<()>();
|
let (shutdown_tx, shutdown_rx) = async_channel::unbounded::<()>();
|
||||||
|
|
||||||
let executor = Arc::new(async_executor::Executor::new());
|
let executor = Arc::new(crate::executor::Executor::new());
|
||||||
|
|
||||||
let num_threads = builder
|
let num_threads = builder
|
||||||
.num_threads
|
.num_threads
|
||||||
@ -344,9 +344,9 @@ impl TaskPool {
|
|||||||
// transmute the lifetimes to 'env here to appease the compiler as it is unable to validate safety.
|
// transmute the lifetimes to 'env here to appease the compiler as it is unable to validate safety.
|
||||||
// Any usages of the references passed into `Scope` must be accessed through
|
// Any usages of the references passed into `Scope` must be accessed through
|
||||||
// the transmuted reference for the rest of this function.
|
// the transmuted reference for the rest of this function.
|
||||||
let executor: &async_executor::Executor = &self.executor;
|
let executor: &crate::executor::Executor = &self.executor;
|
||||||
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
|
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
|
||||||
let executor: &'env async_executor::Executor = unsafe { mem::transmute(executor) };
|
let executor: &'env crate::executor::Executor = unsafe { mem::transmute(executor) };
|
||||||
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
|
// SAFETY: As above, all futures must complete in this function so we can change the lifetime
|
||||||
let external_executor: &'env ThreadExecutor<'env> =
|
let external_executor: &'env ThreadExecutor<'env> =
|
||||||
unsafe { mem::transmute(external_executor) };
|
unsafe { mem::transmute(external_executor) };
|
||||||
@ -432,7 +432,7 @@ impl TaskPool {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn execute_global_external_scope<'scope, 'ticker, T>(
|
async fn execute_global_external_scope<'scope, 'ticker, T>(
|
||||||
executor: &'scope async_executor::Executor<'scope>,
|
executor: &'scope crate::executor::Executor<'scope>,
|
||||||
external_ticker: ThreadExecutorTicker<'scope, 'ticker>,
|
external_ticker: ThreadExecutorTicker<'scope, 'ticker>,
|
||||||
scope_ticker: ThreadExecutorTicker<'scope, 'ticker>,
|
scope_ticker: ThreadExecutorTicker<'scope, 'ticker>,
|
||||||
get_results: impl Future<Output = Vec<T>>,
|
get_results: impl Future<Output = Vec<T>>,
|
||||||
@ -478,7 +478,7 @@ impl TaskPool {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn execute_global_scope<'scope, 'ticker, T>(
|
async fn execute_global_scope<'scope, 'ticker, T>(
|
||||||
executor: &'scope async_executor::Executor<'scope>,
|
executor: &'scope crate::executor::Executor<'scope>,
|
||||||
scope_ticker: ThreadExecutorTicker<'scope, 'ticker>,
|
scope_ticker: ThreadExecutorTicker<'scope, 'ticker>,
|
||||||
get_results: impl Future<Output = Vec<T>>,
|
get_results: impl Future<Output = Vec<T>>,
|
||||||
) -> Vec<T> {
|
) -> Vec<T> {
|
||||||
@ -562,7 +562,7 @@ impl TaskPool {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn with_local_executor<F, R>(&self, f: F) -> R
|
pub fn with_local_executor<F, R>(&self, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&async_executor::LocalExecutor) -> R,
|
F: FnOnce(&crate::executor::LocalExecutor) -> R,
|
||||||
{
|
{
|
||||||
Self::LOCAL_EXECUTOR.with(f)
|
Self::LOCAL_EXECUTOR.with(f)
|
||||||
}
|
}
|
||||||
@ -593,7 +593,7 @@ impl Drop for TaskPool {
|
|||||||
/// For more information, see [`TaskPool::scope`].
|
/// For more information, see [`TaskPool::scope`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scope<'scope, 'env: 'scope, T> {
|
pub struct Scope<'scope, 'env: 'scope, T> {
|
||||||
executor: &'scope async_executor::Executor<'scope>,
|
executor: &'scope crate::executor::Executor<'scope>,
|
||||||
external_executor: &'scope ThreadExecutor<'scope>,
|
external_executor: &'scope ThreadExecutor<'scope>,
|
||||||
scope_executor: &'scope ThreadExecutor<'scope>,
|
scope_executor: &'scope ThreadExecutor<'scope>,
|
||||||
spawned: &'scope ConcurrentQueue<FallibleTask<Result<T, Box<(dyn core::any::Any + Send)>>>>,
|
spawned: &'scope ConcurrentQueue<FallibleTask<Result<T, Box<(dyn core::any::Any + Send)>>>>,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use std::thread::{self, ThreadId};
|
use std::thread::{self, ThreadId};
|
||||||
|
|
||||||
use async_executor::{Executor, Task};
|
use crate::executor::{Executor, Task};
|
||||||
use futures_lite::Future;
|
use futures_lite::Future;
|
||||||
|
|
||||||
/// An executor that can only be ticked on the thread it was instantiated on. But
|
/// An executor that can only be ticked on the thread it was instantiated on. But
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
use super::TaskPool;
|
use super::TaskPool;
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
use spin::Once;
|
||||||
|
|
||||||
macro_rules! taskpool {
|
macro_rules! taskpool {
|
||||||
($(#[$attr:meta])* ($static:ident, $type:ident)) => {
|
($(#[$attr:meta])* ($static:ident, $type:ident)) => {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
static $static: OnceLock<$type> = OnceLock::new();
|
static $static: OnceLock<$type> = OnceLock::new();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
static $static: Once<$type> = Once::new();
|
||||||
|
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct $type(TaskPool);
|
pub struct $type(TaskPool);
|
||||||
@ -13,9 +22,17 @@ macro_rules! taskpool {
|
|||||||
impl $type {
|
impl $type {
|
||||||
#[doc = concat!(" Gets the global [`", stringify!($type), "`] instance, or initializes it with `f`.")]
|
#[doc = concat!(" Gets the global [`", stringify!($type), "`] instance, or initializes it with `f`.")]
|
||||||
pub fn get_or_init(f: impl FnOnce() -> TaskPool) -> &'static Self {
|
pub fn get_or_init(f: impl FnOnce() -> TaskPool) -> &'static Self {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
{
|
||||||
$static.get_or_init(|| Self(f()))
|
$static.get_or_init(|| Self(f()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
{
|
||||||
|
$static.call_once(|| Self(f()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[doc = concat!(" Attempts to get the global [`", stringify!($type), "`] instance, \
|
#[doc = concat!(" Attempts to get the global [`", stringify!($type), "`] instance, \
|
||||||
or returns `None` if it is not initialized.")]
|
or returns `None` if it is not initialized.")]
|
||||||
pub fn try_get() -> Option<&'static Self> {
|
pub fn try_get() -> Option<&'static Self> {
|
||||||
|
@ -62,14 +62,6 @@ impl Prepare for CompileCheckNoStdCommand {
|
|||||||
"Please fix compiler errors in output above for bevy_mikktspace no_std compatibility.",
|
"Please fix compiler errors in output above for bevy_mikktspace no_std compatibility.",
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.push(PreparedCommand::new::<Self>(
|
|
||||||
cmd!(
|
|
||||||
sh,
|
|
||||||
"cargo check -p bevy_mikktspace --no-default-features --features libm --target {target}"
|
|
||||||
),
|
|
||||||
"Please fix compiler errors in output above for bevy_mikktspace no_std compatibility.",
|
|
||||||
));
|
|
||||||
|
|
||||||
commands.push(PreparedCommand::new::<Self>(
|
commands.push(PreparedCommand::new::<Self>(
|
||||||
cmd!(
|
cmd!(
|
||||||
sh,
|
sh,
|
||||||
@ -94,6 +86,14 @@ impl Prepare for CompileCheckNoStdCommand {
|
|||||||
"Please fix compiler errors in output above for bevy_color no_std compatibility.",
|
"Please fix compiler errors in output above for bevy_color no_std compatibility.",
|
||||||
));
|
));
|
||||||
|
|
||||||
|
commands.push(PreparedCommand::new::<Self>(
|
||||||
|
cmd!(
|
||||||
|
sh,
|
||||||
|
"cargo check -p bevy_tasks --no-default-features --features edge_executor,critical-section --target {target}"
|
||||||
|
),
|
||||||
|
"Please fix compiler errors in output above for bevy_tasks no_std compatibility.",
|
||||||
|
));
|
||||||
|
|
||||||
commands
|
commands
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user