Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for non-browser wasm #17499

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions crates/bevy_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ portable-atomic = [
"bevy_reflect?/portable-atomic",
]

## Enables use of browser APIs.
## Note this is currently only applicable on `wasm32` architectures.
browser = [
bushrat011899 marked this conversation as resolved.
Show resolved Hide resolved
"bevy_platform_support/browser",
"bevy_tasks?/browser",
"dep:wasm-bindgen",
"dep:web-sys",
"dep:console_error_panic_hook",
]

[dependencies]
# bevy
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
Expand All @@ -86,14 +96,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"
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1330,7 +1330,10 @@ type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;

fn run_once(mut app: App) -> AppExit {
while app.plugins_state() == PluginsState::Adding {
#[cfg(all(not(target_arch = "wasm32"), feature = "bevy_tasks"))]
#[cfg(all(
not(all(target_arch = "wasm32", feature = "browser")),
feature = "bevy_tasks"
))]
bevy_tasks::tick_global_task_pools_on_main_thread();
}
app.finish();
Expand Down
13 changes: 6 additions & 7 deletions crates/bevy_app/src/panic_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,12 @@ pub struct PanicHandlerPlugin;

impl Plugin for PanicHandlerPlugin {
fn build(&self, _app: &mut App) {
#[cfg(target_arch = "wasm32")]
{
console_error_panic_hook::set_once();
}
#[cfg(not(target_arch = "wasm32"))]
{
// Use the default target panic hook - Do nothing.
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "wasm32", feature = "browser"))] {
console_error_panic_hook::set_once();
} else {
// Use the default target panic hook - Do nothing.
}
}
}
}
103 changes: 52 additions & 51 deletions crates/bevy_app/src/schedule_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "browser"))]
use {
alloc::{boxed::Box, rc::Rc},
core::cell::RefCell,
Expand Down Expand Up @@ -77,7 +77,10 @@ impl Plugin for ScheduleRunnerPlugin {
let plugins_state = app.plugins_state();
if plugins_state != PluginsState::Cleaned {
while app.plugins_state() == PluginsState::Adding {
#[cfg(all(not(target_arch = "wasm32"), feature = "bevy_tasks"))]
#[cfg(all(
not(all(target_arch = "wasm32", feature = "browser")),
feature = "bevy_tasks"
))]
bevy_tasks::tick_global_task_pools_on_main_thread();
}
app.finish();
Expand Down Expand Up @@ -118,58 +121,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 = "browser"))] {
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`.");
}
}
}

#[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 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);
let asap = Duration::from_millis(1);

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 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);

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()
}
}
}
}
Expand Down
62 changes: 32 additions & 30 deletions crates/bevy_app/src/task_pool_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,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(not(target_arch = "wasm32"))]
use bevy_tasks::tick_global_task_pools_on_main_thread;
cfg_if::cfg_if! {
if #[cfg(not(all(target_arch = "wasm32", feature = "browser")))] {
use {crate::Last, bevy_ecs::prelude::NonSend, 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)]
Expand All @@ -32,22 +40,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 = "browser")))]
_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)]
Expand Down Expand Up @@ -184,20 +183,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 = "browser")))]
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()
});
Expand All @@ -213,20 +213,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 = "browser")))]
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()
});
Expand All @@ -242,20 +243,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 = "browser")))]
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()
});
Expand Down
7 changes: 7 additions & 0 deletions crates/bevy_asset/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ web-sys = { version = "0.3", features = [
] }
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
# 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 = [
"browser",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
"browser",
] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
notify-debouncer-full = { version = "0.4.0", optional = true }
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_audio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ cpal = { version = "0.15", optional = true }
rodio = { version = "0.20", default-features = false, features = [
"wasm-bindgen",
] }
# 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 = [
"browser",
] }

[features]
mp3 = ["rodio/mp3"]
Expand Down
12 changes: 12 additions & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,18 @@ bevy_text = { path = "../bevy_text", optional = true, version = "0.16.0-dev" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.16.0-dev" }
bevy_winit = { path = "../bevy_winit", optional = true, 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.
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
"browser",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
"browser",
] }
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [
"browser",
] }

[lints]
workspace = true

Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_log/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
"browser",
] }

[target.'cfg(target_os = "ios")'.dependencies]
tracing-oslog = "0.2"
Expand Down
Loading
Loading