Support for non-browser wasm
(#17499)
# Objective - Contributes to #15460 - Supersedes #8520 - Fixes #4906 ## Solution - Added a new `web` feature to `bevy`, and several of its crates. - Enabled new `web` feature automatically within crates without `no_std` support. ## Testing - `cargo build --no-default-features --target wasm32v1-none` --- ## Migration Guide When using Bevy crates which _don't_ automatically enable the `web` feature, please enable it when building for the browser. ## Notes - I added [`cfg_if`](https://crates.io/crates/cfg-if) to help manage some of the feature gate gore that this extra feature introduces. It's still pretty ugly, but I think much easier to read. - Certain `wasm` targets (e.g., [wasm32-wasip1](https://doc.rust-lang.org/nightly/rustc/platform-support/wasm32-wasip1.html#wasm32-wasip1)) provide an incomplete implementation for `std`. I have not tested these platforms, but I suspect Bevy's liberal use of usually unsupported features (e.g., threading) will cause these targets to fail. As such, consider `wasm32-unknown-unknown` as the only `wasm` platform with support from Bevy for `std`. All others likely will need to be treated as `no_std` platforms.
This commit is contained in:
parent
edba54adac
commit
c6204279eb
@ -513,6 +513,9 @@ critical-section = ["bevy_internal/critical-section"]
|
||||
# Uses the `libm` maths library instead of the one provided in `std` and `core`.
|
||||
libm = ["bevy_internal/libm"]
|
||||
|
||||
# Enables use of browser APIs. Note this is currently only applicable on `wasm32` architectures.
|
||||
web = ["bevy_internal/web"]
|
||||
|
||||
[dependencies]
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, optional = true }
|
||||
|
@ -44,6 +44,7 @@ smallvec = "1"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
||||
|
||||
[lints]
|
||||
|
@ -61,6 +61,17 @@ critical-section = [
|
||||
"bevy_reflect?/critical-section",
|
||||
]
|
||||
|
||||
## Enables use of browser APIs.
|
||||
## Note this is currently only applicable on `wasm32` architectures.
|
||||
web = [
|
||||
"bevy_platform_support/web",
|
||||
"bevy_tasks/web",
|
||||
"bevy_reflect?/web",
|
||||
"dep:wasm-bindgen",
|
||||
"dep:web-sys",
|
||||
"dep:console_error_panic_hook",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
@ -78,14 +89,15 @@ thiserror = { version = "2", default-features = false }
|
||||
variadics_please = "1.1"
|
||||
tracing = { version = "0.1", default-features = false, optional = true }
|
||||
log = { version = "0.4", default-features = false }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
[target.'cfg(any(unix, windows))'.dependencies]
|
||||
ctrlc = { version = "3.4.4", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
web-sys = { version = "0.3", features = ["Window"] }
|
||||
console_error_panic_hook = "0.1.6"
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
web-sys = { version = "0.3", features = ["Window"], optional = true }
|
||||
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
crossbeam-channel = "0.5.0"
|
||||
|
@ -1353,7 +1353,7 @@ type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;
|
||||
|
||||
fn run_once(mut app: App) -> AppExit {
|
||||
while app.plugins_state() == PluginsState::Adding {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
bevy_tasks::tick_global_task_pools_on_main_thread();
|
||||
}
|
||||
app.finish();
|
||||
|
@ -43,21 +43,16 @@ impl Plugin for PanicHandlerPlugin {
|
||||
{
|
||||
static SET_HOOK: std::sync::Once = std::sync::Once::new();
|
||||
SET_HOOK.call_once(|| {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
// This provides better panic handling in JS engines (displays the panic message and improves the backtrace).
|
||||
std::panic::set_hook(alloc::boxed::Box::new(console_error_panic_hook::hook));
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
#[cfg(feature = "error_panic_hook")]
|
||||
{
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
// This provides better panic handling in JS engines (displays the panic message and improves the backtrace).
|
||||
std::panic::set_hook(alloc::boxed::Box::new(console_error_panic_hook::hook));
|
||||
} else if #[cfg(feature = "error_panic_hook")] {
|
||||
let current_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(alloc::boxed::Box::new(
|
||||
bevy_ecs::error::bevy_error_panic_hook(current_hook),
|
||||
));
|
||||
}
|
||||
|
||||
// Otherwise use the default target panic hook - Do nothing.
|
||||
}
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
use bevy_platform_support::time::Instant;
|
||||
use core::time::Duration;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use {
|
||||
alloc::{boxed::Box, rc::Rc},
|
||||
core::cell::RefCell,
|
||||
@ -77,7 +77,7 @@ impl Plugin for ScheduleRunnerPlugin {
|
||||
let plugins_state = app.plugins_state();
|
||||
if plugins_state != PluginsState::Cleaned {
|
||||
while app.plugins_state() == PluginsState::Adding {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
bevy_tasks::tick_global_task_pools_on_main_thread();
|
||||
}
|
||||
app.finish();
|
||||
@ -118,58 +118,56 @@ impl Plugin for ScheduleRunnerPlugin {
|
||||
Ok(None)
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
loop {
|
||||
match tick(&mut app, wait) {
|
||||
Ok(Some(_delay)) => {
|
||||
#[cfg(feature = "std")]
|
||||
std::thread::sleep(_delay);
|
||||
}
|
||||
Ok(None) => continue,
|
||||
Err(exit) => return exit,
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
fn set_timeout(callback: &Closure<dyn FnMut()>, dur: Duration) {
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
callback.as_ref().unchecked_ref(),
|
||||
dur.as_millis() as i32,
|
||||
)
|
||||
.expect("Should register `setTimeout`.");
|
||||
}
|
||||
}
|
||||
}
|
||||
let asap = Duration::from_millis(1);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
fn set_timeout(callback: &Closure<dyn FnMut()>, dur: Duration) {
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
callback.as_ref().unchecked_ref(),
|
||||
dur.as_millis() as i32,
|
||||
)
|
||||
.expect("Should register `setTimeout`.");
|
||||
}
|
||||
let asap = Duration::from_millis(1);
|
||||
let exit = Rc::new(RefCell::new(AppExit::Success));
|
||||
let closure_exit = exit.clone();
|
||||
|
||||
let exit = Rc::new(RefCell::new(AppExit::Success));
|
||||
let closure_exit = exit.clone();
|
||||
let mut app = Rc::new(app);
|
||||
let moved_tick_closure = Rc::new(RefCell::new(None));
|
||||
let base_tick_closure = moved_tick_closure.clone();
|
||||
|
||||
let mut app = Rc::new(app);
|
||||
let moved_tick_closure = Rc::new(RefCell::new(None));
|
||||
let base_tick_closure = moved_tick_closure.clone();
|
||||
let tick_app = move || {
|
||||
let app = Rc::get_mut(&mut app).unwrap();
|
||||
let delay = tick(app, wait);
|
||||
match delay {
|
||||
Ok(delay) => set_timeout(
|
||||
moved_tick_closure.borrow().as_ref().unwrap(),
|
||||
delay.unwrap_or(asap),
|
||||
),
|
||||
Err(code) => {
|
||||
closure_exit.replace(code);
|
||||
}
|
||||
}
|
||||
};
|
||||
*base_tick_closure.borrow_mut() =
|
||||
Some(Closure::wrap(Box::new(tick_app) as Box<dyn FnMut()>));
|
||||
set_timeout(base_tick_closure.borrow().as_ref().unwrap(), asap);
|
||||
|
||||
let tick_app = move || {
|
||||
let app = Rc::get_mut(&mut app).unwrap();
|
||||
let delay = tick(app, wait);
|
||||
match delay {
|
||||
Ok(delay) => set_timeout(
|
||||
moved_tick_closure.borrow().as_ref().unwrap(),
|
||||
delay.unwrap_or(asap),
|
||||
),
|
||||
Err(code) => {
|
||||
closure_exit.replace(code);
|
||||
exit.take()
|
||||
} else {
|
||||
loop {
|
||||
match tick(&mut app, wait) {
|
||||
Ok(Some(_delay)) => {
|
||||
#[cfg(feature = "std")]
|
||||
std::thread::sleep(_delay);
|
||||
}
|
||||
Ok(None) => continue,
|
||||
Err(exit) => return exit,
|
||||
}
|
||||
}
|
||||
};
|
||||
*base_tick_closure.borrow_mut() =
|
||||
Some(Closure::wrap(Box::new(tick_app) as Box<dyn FnMut()>));
|
||||
set_timeout(base_tick_closure.borrow().as_ref().unwrap(), asap);
|
||||
|
||||
exit.take()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,19 @@ use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuil
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use log::trace;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use {crate::Last, bevy_ecs::prelude::NonSend};
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(all(target_arch = "wasm32", feature = "web")))] {
|
||||
use {crate::Last, bevy_ecs::prelude::NonSend, bevy_tasks::tick_global_task_pools_on_main_thread};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use bevy_tasks::tick_global_task_pools_on_main_thread;
|
||||
/// A system used to check and advanced our task pools.
|
||||
///
|
||||
/// Calls [`tick_global_task_pools_on_main_thread`],
|
||||
/// and uses [`NonSendMarker`] to ensure that this system runs on the main thread
|
||||
fn tick_global_task_pools(_main_thread_marker: Option<NonSend<NonSendMarker>>) {
|
||||
tick_global_task_pools_on_main_thread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Setup of default task pools: [`AsyncComputeTaskPool`], [`ComputeTaskPool`], [`IoTaskPool`].
|
||||
#[derive(Default)]
|
||||
@ -24,22 +32,13 @@ impl Plugin for TaskPoolPlugin {
|
||||
// Setup the default bevy task pools
|
||||
self.task_pool_options.create_default_pools();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
_app.add_systems(Last, tick_global_task_pools);
|
||||
}
|
||||
}
|
||||
/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread.
|
||||
pub struct NonSendMarker(PhantomData<*mut ()>);
|
||||
|
||||
/// A system used to check and advanced our task pools.
|
||||
///
|
||||
/// Calls [`tick_global_task_pools_on_main_thread`],
|
||||
/// and uses [`NonSendMarker`] to ensure that this system runs on the main thread
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn tick_global_task_pools(_main_thread_marker: Option<NonSend<NonSendMarker>>) {
|
||||
tick_global_task_pools_on_main_thread();
|
||||
}
|
||||
|
||||
/// Defines a simple way to determine how many threads to use given the number of remaining cores
|
||||
/// and number of total cores
|
||||
#[derive(Clone)]
|
||||
@ -176,20 +175,21 @@ impl TaskPoolOptions {
|
||||
remaining_threads = remaining_threads.saturating_sub(io_threads);
|
||||
|
||||
IoTaskPool::get_or_init(|| {
|
||||
#[cfg_attr(target_arch = "wasm32", expect(unused_mut))]
|
||||
let mut builder = TaskPoolBuilder::default()
|
||||
let builder = TaskPoolBuilder::default()
|
||||
.num_threads(io_threads)
|
||||
.thread_name("IO Task Pool".to_string());
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
let builder = {
|
||||
let mut builder = builder;
|
||||
if let Some(f) = self.io.on_thread_spawn.clone() {
|
||||
builder = builder.on_thread_spawn(move || f());
|
||||
}
|
||||
if let Some(f) = self.io.on_thread_destroy.clone() {
|
||||
builder = builder.on_thread_destroy(move || f());
|
||||
}
|
||||
}
|
||||
builder
|
||||
};
|
||||
|
||||
builder.build()
|
||||
});
|
||||
@ -205,20 +205,21 @@ impl TaskPoolOptions {
|
||||
remaining_threads = remaining_threads.saturating_sub(async_compute_threads);
|
||||
|
||||
AsyncComputeTaskPool::get_or_init(|| {
|
||||
#[cfg_attr(target_arch = "wasm32", expect(unused_mut))]
|
||||
let mut builder = TaskPoolBuilder::default()
|
||||
let builder = TaskPoolBuilder::default()
|
||||
.num_threads(async_compute_threads)
|
||||
.thread_name("Async Compute Task Pool".to_string());
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
let builder = {
|
||||
let mut builder = builder;
|
||||
if let Some(f) = self.async_compute.on_thread_spawn.clone() {
|
||||
builder = builder.on_thread_spawn(move || f());
|
||||
}
|
||||
if let Some(f) = self.async_compute.on_thread_destroy.clone() {
|
||||
builder = builder.on_thread_destroy(move || f());
|
||||
}
|
||||
}
|
||||
builder
|
||||
};
|
||||
|
||||
builder.build()
|
||||
});
|
||||
@ -234,20 +235,21 @@ impl TaskPoolOptions {
|
||||
trace!("Compute Threads: {}", compute_threads);
|
||||
|
||||
ComputeTaskPool::get_or_init(|| {
|
||||
#[cfg_attr(target_arch = "wasm32", expect(unused_mut))]
|
||||
let mut builder = TaskPoolBuilder::default()
|
||||
let builder = TaskPoolBuilder::default()
|
||||
.num_threads(compute_threads)
|
||||
.thread_name("Compute Task Pool".to_string());
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
let builder = {
|
||||
let mut builder = builder;
|
||||
if let Some(f) = self.compute.on_thread_spawn.clone() {
|
||||
builder = builder.on_thread_spawn(move || f());
|
||||
}
|
||||
if let Some(f) = self.compute.on_thread_destroy.clone() {
|
||||
builder = builder.on_thread_destroy(move || f());
|
||||
}
|
||||
}
|
||||
builder
|
||||
};
|
||||
|
||||
builder.build()
|
||||
});
|
||||
|
@ -57,6 +57,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
web-sys = { version = "0.3", features = [
|
||||
"Window",
|
||||
@ -66,6 +67,15 @@ web-sys = { version = "0.3", features = [
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
notify-debouncer-full = { version = "0.5.0", optional = true }
|
||||
|
@ -26,9 +26,16 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
cpal = { version = "0.15", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
rodio = { version = "0.20", default-features = false, features = [
|
||||
"wasm-bindgen",
|
||||
] }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
[features]
|
||||
mp3 = ["rodio/mp3"]
|
||||
|
@ -333,6 +333,15 @@ libm = [
|
||||
# This backend is incompatible with `no_std` targets.
|
||||
async_executor = ["std", "bevy_tasks/async_executor", "bevy_ecs/async_executor"]
|
||||
|
||||
# Enables use of browser APIs.
|
||||
# Note this is currently only applicable on `wasm32` architectures.
|
||||
web = [
|
||||
"bevy_app/web",
|
||||
"bevy_platform_support/web",
|
||||
"bevy_reflect/web",
|
||||
"bevy_tasks/web",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
# bevy (no_std)
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
|
@ -38,6 +38,10 @@ android_log-sys = "0.3.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tracing-wasm = "0.2.1"
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
tracing-oslog = "0.2"
|
||||
|
@ -36,6 +36,7 @@ uuid = { version = "1.13.1", features = ["v4"] }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
||||
|
||||
[lints]
|
||||
|
@ -30,13 +30,19 @@ std = [
|
||||
"foldhash/std",
|
||||
]
|
||||
|
||||
## Allows access to the `alloc` crate.
|
||||
alloc = ["portable-atomic-util/alloc", "dep:hashbrown"]
|
||||
|
||||
## `critical-section` provides the building blocks for synchronization primitives
|
||||
## on all platforms, including `no_std`.
|
||||
critical-section = ["dep:critical-section", "portable-atomic/critical-section"]
|
||||
|
||||
## Enables use of browser APIs.
|
||||
## Note this is currently only applicable on `wasm32` architectures.
|
||||
web = ["dep:web-time", "dep:getrandom"]
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0.0"
|
||||
critical-section = { version = "1.2.0", default-features = false, optional = true }
|
||||
spin = { version = "0.9.8", default-features = false, features = [
|
||||
"mutex",
|
||||
@ -53,8 +59,10 @@ hashbrown = { version = "0.15.1", features = [
|
||||
], optional = true, default-features = false }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
web-time = { version = "1.1", default-features = false }
|
||||
getrandom = { version = "0.2.0", default-features = false, features = ["js"] }
|
||||
web-time = { version = "1.1", default-features = false, optional = true }
|
||||
getrandom = { version = "0.2.0", default-features = false, optional = true, features = [
|
||||
"js",
|
||||
] }
|
||||
|
||||
[target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies]
|
||||
portable-atomic = { version = "1", default-features = false, features = [
|
||||
|
@ -1,194 +0,0 @@
|
||||
//! Provides `Instant` for all platforms.
|
||||
|
||||
pub use time::Instant;
|
||||
|
||||
// TODO: Create a `web` feature to enable WASI compatibility.
|
||||
// See https://github.com/bevyengine/bevy/issues/4906
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_time as time;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
|
||||
use std::time;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "std")))]
|
||||
use fallback as time;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "std")))]
|
||||
mod fallback {
|
||||
//! Provides a fallback implementation of `Instant` from the standard library.
|
||||
|
||||
#![expect(
|
||||
unsafe_code,
|
||||
reason = "Instant fallback requires unsafe to allow users to update the internal value"
|
||||
)]
|
||||
|
||||
use crate::sync::atomic::{AtomicPtr, Ordering};
|
||||
|
||||
use core::{
|
||||
fmt,
|
||||
ops::{Add, AddAssign, Sub, SubAssign},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
static ELAPSED_GETTER: AtomicPtr<()> = AtomicPtr::new(unset_getter as *mut _);
|
||||
|
||||
/// Fallback implementation of `Instant` suitable for a `no_std` environment.
|
||||
///
|
||||
/// If you are on any of the following target architectures, this is a drop-in replacement:
|
||||
///
|
||||
/// - `x86`
|
||||
/// - `x86_64`
|
||||
/// - `aarch64`
|
||||
///
|
||||
/// On any other architecture, you must call [`Instant::set_elapsed`], providing a method
|
||||
/// which when called supplies a monotonically increasing count of elapsed nanoseconds relative
|
||||
/// to some arbitrary point in time.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Instant(Duration);
|
||||
|
||||
impl Instant {
|
||||
/// Returns an instant corresponding to "now".
|
||||
#[must_use]
|
||||
pub fn now() -> Instant {
|
||||
let getter = ELAPSED_GETTER.load(Ordering::Acquire);
|
||||
|
||||
// SAFETY: Function pointer is always valid
|
||||
let getter = unsafe { core::mem::transmute::<_, fn() -> Duration>(getter) };
|
||||
|
||||
Self((getter)())
|
||||
}
|
||||
|
||||
/// Provides a function returning the amount of time that has elapsed since execution began.
|
||||
/// The getter provided to this method will be used by [`now`](Instant::now).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - The function provided must accurately represent the elapsed time.
|
||||
/// - The function must preserve all invariants of the [`Instant`] type.
|
||||
/// - The pointer to the function must be valid whenever [`Instant::now`] is called.
|
||||
pub unsafe fn set_elapsed(getter: fn() -> Duration) {
|
||||
ELAPSED_GETTER.store(getter as *mut _, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one,
|
||||
/// or zero duration if that instant is later than this one.
|
||||
#[must_use]
|
||||
pub fn duration_since(&self, earlier: Instant) -> Duration {
|
||||
self.saturating_duration_since(earlier)
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one,
|
||||
/// or None if that instant is later than this one.
|
||||
///
|
||||
/// Due to monotonicity bugs, even under correct logical ordering of the passed `Instant`s,
|
||||
/// this method can return `None`.
|
||||
#[must_use]
|
||||
pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
|
||||
self.0.checked_sub(earlier.0)
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one,
|
||||
/// or zero duration if that instant is later than this one.
|
||||
#[must_use]
|
||||
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
|
||||
self.0.saturating_sub(earlier.0)
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed since this instant.
|
||||
#[must_use]
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
self.saturating_duration_since(Instant::now())
|
||||
}
|
||||
|
||||
/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
|
||||
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
|
||||
/// otherwise.
|
||||
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
|
||||
self.0.checked_add(duration).map(Instant)
|
||||
}
|
||||
|
||||
/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
|
||||
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
|
||||
/// otherwise.
|
||||
pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
|
||||
self.0.checked_sub(duration).map(Instant)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// This function may panic if the resulting point in time cannot be represented by the
|
||||
/// underlying data structure. See [`Instant::checked_add`] for a version without panic.
|
||||
fn add(self, other: Duration) -> Instant {
|
||||
self.checked_add(other)
|
||||
.expect("overflow when adding duration to instant")
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Duration> for Instant {
|
||||
fn add_assign(&mut self, other: Duration) {
|
||||
*self = *self + other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
fn sub(self, other: Duration) -> Instant {
|
||||
self.checked_sub(other)
|
||||
.expect("overflow when subtracting duration from instant")
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Duration> for Instant {
|
||||
fn sub_assign(&mut self, other: Duration) {
|
||||
*self = *self - other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Instant> for Instant {
|
||||
type Output = Duration;
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one,
|
||||
/// or zero duration if that instant is later than this one.
|
||||
fn sub(self, other: Instant) -> Duration {
|
||||
self.duration_since(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Instant {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn unset_getter() -> Duration {
|
||||
let _nanos: u64;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe {
|
||||
_nanos = core::arch::x86::_rdtsc();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe {
|
||||
_nanos = core::arch::x86_64::_rdtsc();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe {
|
||||
let mut ticks: u64;
|
||||
core::arch::asm!("mrs {}, cntvct_el0", out(reg) ticks);
|
||||
_nanos = ticks;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
panic!("An elapsed time getter has not been provided to `Instant`. Please use `Instant::set_elapsed(...)` before calling `Instant::now()`");
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
return Duration::from_nanos(_nanos);
|
||||
}
|
||||
}
|
176
crates/bevy_platform_support/src/time/fallback.rs
Normal file
176
crates/bevy_platform_support/src/time/fallback.rs
Normal file
@ -0,0 +1,176 @@
|
||||
//! Provides a fallback implementation of `Instant` from the standard library.
|
||||
|
||||
#![expect(
|
||||
unsafe_code,
|
||||
reason = "Instant fallback requires unsafe to allow users to update the internal value"
|
||||
)]
|
||||
|
||||
use crate::sync::atomic::{AtomicPtr, Ordering};
|
||||
|
||||
use core::{
|
||||
fmt,
|
||||
ops::{Add, AddAssign, Sub, SubAssign},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
static ELAPSED_GETTER: AtomicPtr<()> = AtomicPtr::new(unset_getter as *mut _);
|
||||
|
||||
/// Fallback implementation of `Instant` suitable for a `no_std` environment.
|
||||
///
|
||||
/// If you are on any of the following target architectures, this is a drop-in replacement:
|
||||
///
|
||||
/// - `x86`
|
||||
/// - `x86_64`
|
||||
/// - `aarch64`
|
||||
///
|
||||
/// On any other architecture, you must call [`Instant::set_elapsed`], providing a method
|
||||
/// which when called supplies a monotonically increasing count of elapsed nanoseconds relative
|
||||
/// to some arbitrary point in time.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Instant(Duration);
|
||||
|
||||
impl Instant {
|
||||
/// Returns an instant corresponding to "now".
|
||||
#[must_use]
|
||||
pub fn now() -> Instant {
|
||||
let getter = ELAPSED_GETTER.load(Ordering::Acquire);
|
||||
|
||||
// SAFETY: Function pointer is always valid
|
||||
let getter = unsafe { core::mem::transmute::<_, fn() -> Duration>(getter) };
|
||||
|
||||
Self((getter)())
|
||||
}
|
||||
|
||||
/// Provides a function returning the amount of time that has elapsed since execution began.
|
||||
/// The getter provided to this method will be used by [`now`](Instant::now).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - The function provided must accurately represent the elapsed time.
|
||||
/// - The function must preserve all invariants of the [`Instant`] type.
|
||||
/// - The pointer to the function must be valid whenever [`Instant::now`] is called.
|
||||
pub unsafe fn set_elapsed(getter: fn() -> Duration) {
|
||||
ELAPSED_GETTER.store(getter as *mut _, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one,
|
||||
/// or zero duration if that instant is later than this one.
|
||||
#[must_use]
|
||||
pub fn duration_since(&self, earlier: Instant) -> Duration {
|
||||
self.saturating_duration_since(earlier)
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one,
|
||||
/// or None if that instant is later than this one.
|
||||
///
|
||||
/// Due to monotonicity bugs, even under correct logical ordering of the passed `Instant`s,
|
||||
/// this method can return `None`.
|
||||
#[must_use]
|
||||
pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
|
||||
self.0.checked_sub(earlier.0)
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one,
|
||||
/// or zero duration if that instant is later than this one.
|
||||
#[must_use]
|
||||
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
|
||||
self.0.saturating_sub(earlier.0)
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed since this instant.
|
||||
#[must_use]
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
self.saturating_duration_since(Instant::now())
|
||||
}
|
||||
|
||||
/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
|
||||
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
|
||||
/// otherwise.
|
||||
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
|
||||
self.0.checked_add(duration).map(Instant)
|
||||
}
|
||||
|
||||
/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
|
||||
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
|
||||
/// otherwise.
|
||||
pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
|
||||
self.0.checked_sub(duration).map(Instant)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// This function may panic if the resulting point in time cannot be represented by the
|
||||
/// underlying data structure. See [`Instant::checked_add`] for a version without panic.
|
||||
fn add(self, other: Duration) -> Instant {
|
||||
self.checked_add(other)
|
||||
.expect("overflow when adding duration to instant")
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Duration> for Instant {
|
||||
fn add_assign(&mut self, other: Duration) {
|
||||
*self = *self + other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
fn sub(self, other: Duration) -> Instant {
|
||||
self.checked_sub(other)
|
||||
.expect("overflow when subtracting duration from instant")
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Duration> for Instant {
|
||||
fn sub_assign(&mut self, other: Duration) {
|
||||
*self = *self - other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Instant> for Instant {
|
||||
type Output = Duration;
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one,
|
||||
/// or zero duration if that instant is later than this one.
|
||||
fn sub(self, other: Instant) -> Duration {
|
||||
self.duration_since(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Instant {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn unset_getter() -> Duration {
|
||||
let _nanos: u64;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe {
|
||||
_nanos = core::arch::x86::_rdtsc();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe {
|
||||
_nanos = core::arch::x86_64::_rdtsc();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe {
|
||||
let mut ticks: u64;
|
||||
core::arch::asm!("mrs {}, cntvct_el0", out(reg) ticks);
|
||||
_nanos = ticks;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
panic!("An elapsed time getter has not been provided to `Instant`. Please use `Instant::set_elapsed(...)` before calling `Instant::now()`");
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
return Duration::from_nanos(_nanos);
|
||||
}
|
15
crates/bevy_platform_support/src/time/mod.rs
Normal file
15
crates/bevy_platform_support/src/time/mod.rs
Normal file
@ -0,0 +1,15 @@
|
||||
//! Provides `Instant` for all platforms.
|
||||
|
||||
pub use time::Instant;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
use web_time as time;
|
||||
} else if #[cfg(feature = "std")] {
|
||||
use std::time;
|
||||
} else {
|
||||
mod fallback;
|
||||
|
||||
use fallback as time;
|
||||
}
|
||||
}
|
@ -69,6 +69,10 @@ critical-section = [
|
||||
"bevy_utils/critical-section",
|
||||
]
|
||||
|
||||
## Enables use of browser APIs.
|
||||
## Note this is currently only applicable on `wasm32` architectures.
|
||||
web = ["bevy_platform_support/web", "uuid?/js"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_reflect_derive = { path = "derive", version = "0.16.0-dev" }
|
||||
@ -111,9 +115,6 @@ wgpu-types = { version = "24", features = [
|
||||
"serde",
|
||||
], optional = true, default-features = false }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ron = "0.8.0"
|
||||
rmp-serde = "1.1"
|
||||
|
@ -26,6 +26,7 @@ syn = { version = "2.0", features = ["full"] }
|
||||
uuid = { version = "1.13.1", features = ["v4"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
||||
|
||||
[lints]
|
||||
|
@ -123,6 +123,19 @@ web-sys = { version = "0.3.67", features = [
|
||||
'Window',
|
||||
] }
|
||||
wasm-bindgen = "0.2"
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_feature = "atomics"))'.dependencies]
|
||||
send_wrapper = "0.6.0"
|
||||
|
@ -38,6 +38,7 @@ thiserror = { version = "2", default-features = false }
|
||||
derive_more = { version = "1", default-features = false, features = ["from"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -10,16 +10,42 @@ keywords = ["bevy"]
|
||||
|
||||
[features]
|
||||
default = ["std", "async_executor"]
|
||||
|
||||
# Functionality
|
||||
|
||||
## Enables multi-threading support.
|
||||
## Without this feature, all tasks will be run on a single thread.
|
||||
multi_threaded = ["std", "dep:async-channel", "dep:concurrent-queue"]
|
||||
|
||||
## Uses `async-executor` as a task execution backend.
|
||||
## This backend is incompatible with `no_std` targets.
|
||||
async_executor = ["std", "dep:async-executor"]
|
||||
|
||||
# Platform Compatibility
|
||||
|
||||
## Allows access to the `std` crate. Enabling this feature will prevent compilation
|
||||
## on `no_std` targets, but provides access to certain additional features on
|
||||
## supported platforms.
|
||||
std = [
|
||||
"futures-lite/std",
|
||||
"async-task/std",
|
||||
"bevy_platform_support/std",
|
||||
"once_cell/std",
|
||||
]
|
||||
multi_threaded = ["std", "dep:async-channel", "dep:concurrent-queue"]
|
||||
async_executor = ["std", "dep:async-executor"]
|
||||
|
||||
## `critical-section` provides the building blocks for synchronization primitives
|
||||
## on all platforms, including `no_std`.
|
||||
critical-section = ["bevy_platform_support/critical-section"]
|
||||
|
||||
## Enables use of browser APIs.
|
||||
## Note this is currently only applicable on `wasm32` architectures.
|
||||
web = [
|
||||
"bevy_platform_support/web",
|
||||
"dep:wasm-bindgen-futures",
|
||||
"dep:pin-project",
|
||||
"dep:futures-channel",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [
|
||||
"alloc",
|
||||
@ -33,6 +59,7 @@ derive_more = { version = "1", default-features = false, features = [
|
||||
"deref",
|
||||
"deref_mut",
|
||||
] }
|
||||
cfg-if = "1.0.0"
|
||||
async-executor = { version = "1.11", optional = true }
|
||||
async-channel = { version = "2.3.0", optional = true }
|
||||
async-io = { version = "2.0.0", optional = true }
|
||||
@ -46,9 +73,9 @@ crossbeam-queue = { version = "0.3", default-features = false, features = [
|
||||
] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen-futures = "0.4"
|
||||
pin-project = "1"
|
||||
futures-channel = "0.3"
|
||||
wasm-bindgen-futures = { version = "0.4", optional = true }
|
||||
pin-project = { version = "1", optional = true }
|
||||
futures-channel = { version = "0.3", optional = true }
|
||||
|
||||
[target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies]
|
||||
async-task = { version = "4.4.0", default-features = false, features = [
|
||||
@ -61,9 +88,6 @@ atomic-waker = { version = "1", default-features = false, features = [
|
||||
"portable-atomic",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
web-time = { version = "1.1" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
|
@ -2,8 +2,9 @@
|
||||
//! for 100ms. It's expected to take about a second to run (assuming the machine has >= 4 logical
|
||||
//! cores)
|
||||
|
||||
use bevy_platform_support::time::Instant;
|
||||
use bevy_tasks::TaskPoolBuilder;
|
||||
use web_time::{Duration, Instant};
|
||||
use core::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let pool = TaskPoolBuilder::new()
|
||||
|
@ -2,8 +2,9 @@
|
||||
//! spinning. Other than the one thread, the system should remain idle, demonstrating good behavior
|
||||
//! for small workloads.
|
||||
|
||||
use bevy_platform_support::time::Instant;
|
||||
use bevy_tasks::TaskPoolBuilder;
|
||||
use web_time::{Duration, Instant};
|
||||
use core::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let pool = TaskPoolBuilder::new()
|
||||
|
@ -14,21 +14,19 @@ use core::{
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "async_executor")] {
|
||||
type ExecutorInner<'a> = async_executor::Executor<'a>;
|
||||
type LocalExecutorInner<'a> = async_executor::LocalExecutor<'a>;
|
||||
} else {
|
||||
type ExecutorInner<'a> = crate::edge_executor::Executor<'a, 64>;
|
||||
type LocalExecutorInner<'a> = crate::edge_executor::LocalExecutor<'a, 64>;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "multi_threaded", not(target_arch = "wasm32")))]
|
||||
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(not(feature = "async_executor"))]
|
||||
type ExecutorInner<'a> = crate::edge_executor::Executor<'a, 64>;
|
||||
|
||||
#[cfg(not(feature = "async_executor"))]
|
||||
type LocalExecutorInner<'a> = crate::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.
|
||||
|
@ -11,19 +11,20 @@ extern crate std;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod conditional_send {
|
||||
/// Use [`ConditionalSend`] to mark an optional Send trait bound. Useful as on certain platforms (eg. Wasm),
|
||||
/// futures aren't Send.
|
||||
pub trait ConditionalSend: Send {}
|
||||
impl<T: Send> ConditionalSend for T {}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[expect(missing_docs, reason = "Not all docs are written yet (#3492).")]
|
||||
mod conditional_send {
|
||||
pub trait ConditionalSend {}
|
||||
impl<T> ConditionalSend for T {}
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_arch = "wasm32")] {
|
||||
/// Use [`ConditionalSend`] to mark an optional Send trait bound. Useful as on certain platforms (eg. Wasm),
|
||||
/// futures aren't Send.
|
||||
pub trait ConditionalSend {}
|
||||
impl<T> ConditionalSend for T {}
|
||||
} else {
|
||||
/// Use [`ConditionalSend`] to mark an optional Send trait bound. Useful as on certain platforms (eg. Wasm),
|
||||
/// futures aren't Send.
|
||||
pub trait ConditionalSend: Send {}
|
||||
impl<T: Send> ConditionalSend for T {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use conditional_send::*;
|
||||
@ -48,38 +49,40 @@ mod executor;
|
||||
mod slice;
|
||||
pub use slice::{ParallelSlice, ParallelSliceMut};
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", path = "wasm_task.rs")]
|
||||
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), path = "wasm_task.rs")]
|
||||
mod task;
|
||||
|
||||
pub use task::Task;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
|
||||
mod task_pool;
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] {
|
||||
mod task_pool;
|
||||
mod thread_executor;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
|
||||
pub use task_pool::{Scope, TaskPool, TaskPoolBuilder};
|
||||
pub use task_pool::{Scope, TaskPool, TaskPoolBuilder};
|
||||
pub use thread_executor::{ThreadExecutor, ThreadExecutorTicker};
|
||||
} else if #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))] {
|
||||
mod single_threaded_task_pool;
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
|
||||
mod single_threaded_task_pool;
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
|
||||
pub use single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder, ThreadExecutor};
|
||||
pub use single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder, ThreadExecutor};
|
||||
}
|
||||
}
|
||||
|
||||
mod usages;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use usages::tick_global_task_pools_on_main_thread;
|
||||
pub use futures_lite::future::poll_once;
|
||||
pub use usages::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool};
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
|
||||
mod thread_executor;
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
|
||||
pub use thread_executor::{ThreadExecutor, ThreadExecutorTicker};
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
pub use usages::tick_global_task_pools_on_main_thread;
|
||||
|
||||
#[cfg(all(feature = "async-io", feature = "std"))]
|
||||
pub use async_io::block_on;
|
||||
#[cfg(all(not(feature = "async-io"), feature = "std"))]
|
||||
pub use futures_lite::future::block_on;
|
||||
pub use futures_lite::future::poll_once;
|
||||
#[cfg(feature = "std")]
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "async-io")] {
|
||||
pub use async_io::block_on;
|
||||
} else {
|
||||
pub use futures_lite::future::block_on;
|
||||
}
|
||||
}
|
||||
|
||||
mod iter;
|
||||
pub use iter::ParallelIterator;
|
||||
@ -102,27 +105,28 @@ pub mod prelude {
|
||||
pub use crate::block_on;
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use core::num::NonZero;
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "std")] {
|
||||
use core::num::NonZero;
|
||||
|
||||
/// Gets the logical CPU core count available to the current process.
|
||||
///
|
||||
/// This is identical to [`std::thread::available_parallelism`], except
|
||||
/// it will return a default value of 1 if it internally errors out.
|
||||
///
|
||||
/// This will always return at least 1.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn available_parallelism() -> usize {
|
||||
std::thread::available_parallelism()
|
||||
.map(NonZero::<usize>::get)
|
||||
.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
|
||||
/// Gets the logical CPU core count available to the current process.
|
||||
///
|
||||
/// This is identical to [`std::thread::available_parallelism`], except
|
||||
/// it will return a default value of 1 if it internally errors out.
|
||||
///
|
||||
/// This will always return at least 1.
|
||||
pub fn available_parallelism() -> usize {
|
||||
std::thread::available_parallelism()
|
||||
.map(NonZero::<usize>::get)
|
||||
.unwrap_or(1)
|
||||
}
|
||||
} else {
|
||||
/// Gets the logical CPU core count available to the current process.
|
||||
///
|
||||
/// This will always return at least 1.
|
||||
pub fn available_parallelism() -> usize {
|
||||
// Without access to std, assume a single thread is available
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,26 +199,27 @@ impl TaskPool {
|
||||
where
|
||||
T: 'static + MaybeSend + MaybeSync,
|
||||
{
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
return Task::wrap_future(future);
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
return Task::wrap_future(future);
|
||||
} else if #[cfg(feature = "std")] {
|
||||
return LOCAL_EXECUTOR.with(|executor| {
|
||||
let task = executor.spawn(future);
|
||||
// Loop until all tasks are done
|
||||
while executor.try_tick() {}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
|
||||
return LOCAL_EXECUTOR.with(|executor| {
|
||||
let task = executor.spawn(future);
|
||||
// Loop until all tasks are done
|
||||
while executor.try_tick() {}
|
||||
Task::new(task)
|
||||
});
|
||||
} else {
|
||||
return {
|
||||
let task = LOCAL_EXECUTOR.spawn(future);
|
||||
// Loop until all tasks are done
|
||||
while LOCAL_EXECUTOR.try_tick() {}
|
||||
|
||||
Task::new(task)
|
||||
});
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "std")))]
|
||||
return {
|
||||
let task = LOCAL_EXECUTOR.spawn(future);
|
||||
// Loop until all tasks are done
|
||||
while LOCAL_EXECUTOR.try_tick() {}
|
||||
|
||||
Task::new(task)
|
||||
};
|
||||
Task::new(task)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a static future on the JS event loop. This is exactly the same as [`TaskPool::spawn`].
|
||||
|
@ -81,7 +81,7 @@ taskpool! {
|
||||
/// # Warning
|
||||
///
|
||||
/// This function *must* be called on the main thread, or the task pools will not be updated appropriately.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
pub fn tick_global_task_pools_on_main_thread() {
|
||||
COMPUTE_TASK_POOL
|
||||
.get()
|
||||
|
@ -66,6 +66,19 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
web-sys = "0.3"
|
||||
crossbeam-channel = "0.5"
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -119,6 +119,7 @@ The default feature set enables most of the expected features of a game engine,
|
||||
|track_location|Enables source location tracking for change detection and spawning/despawning, which can assist with debugging|
|
||||
|wav|WAV audio format support|
|
||||
|wayland|Wayland display server support|
|
||||
|web|Enables use of browser APIs. Note this is currently only applicable on `wasm32` architectures.|
|
||||
|webgpu|Enable support for WebGPU in Wasm. When enabled, this feature will override the `webgl2` feature and you won't be able to run Wasm builds with WebGL2, only with WebGPU.|
|
||||
|webp|WebP image format support|
|
||||
|zlib|For KTX2 supercompression|
|
||||
|
Loading…
Reference in New Issue
Block a user