From 2b0ee24a5d213ad1d2e639ce6ab3f570a979ed28 Mon Sep 17 00:00:00 2001 From: Tomasz Sterna Date: Wed, 16 Sep 2020 03:05:31 +0200 Subject: [PATCH] Implement single threaded task scheduler for WebAssembly (#496) * Add hello_wasm example * Implement single threaded task scheduler for WebAssembly --- Cargo.toml | 15 +++ crates/bevy_app/Cargo.toml | 5 + crates/bevy_app/src/schedule_runner.rs | 95 +++++++++++---- crates/bevy_tasks/src/lib.rs | 7 ++ .../src/single_threaded_task_pool.rs | 112 ++++++++++++++++++ crates/bevy_window/Cargo.toml | 3 + examples/README.md | 18 ++- examples/wasm/.gitignore | 1 + examples/wasm/favicon.ico | Bin 0 -> 67646 bytes examples/wasm/headless_wasm.rs | 32 +++++ examples/wasm/hello_wasm.rs | 14 +++ examples/wasm/index.html | 9 ++ 12 files changed, 287 insertions(+), 24 deletions(-) create mode 100644 crates/bevy_tasks/src/single_threaded_task_pool.rs create mode 100644 examples/wasm/.gitignore create mode 100644 examples/wasm/favicon.ico create mode 100644 examples/wasm/headless_wasm.rs create mode 100644 examples/wasm/hello_wasm.rs create mode 100644 examples/wasm/index.html diff --git a/Cargo.toml b/Cargo.toml index de6d4d9113..3347bf7eae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,11 @@ bevy_winit = { path = "crates/bevy_winit", optional = true, version = "0.1" } [dev-dependencies] rand = "0.7.3" serde = { version = "1", features = ["derive"] } +log = "0.4" + +#wasm +console_error_panic_hook = "0.1.6" +console_log = { version = "0.2", features = ["color"] } [[example]] name = "hello_world" @@ -255,3 +260,13 @@ path = "examples/window/multiple_windows.rs" [[example]] name = "window_settings" path = "examples/window/window_settings.rs" + +[[example]] +name = "hello_wasm" +path = "examples/wasm/hello_wasm.rs" +required-features = [] + +[[example]] +name = "headless_wasm" +path = "examples/wasm/headless_wasm.rs" +required-features = [] diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index 5b4506e5ad..8be8fd53dd 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -26,3 +26,8 @@ bevy_math = { path = "../bevy_math", version = "0.1" } libloading = { version = "0.6", optional = true } log = { version = "0.4", features = ["release_max_level_info"] } serde = { version = "1.0", features = ["derive"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { version = "0.2" } +web-sys = { version = "0.3", features = [ "Window" ] } +wasm-timer = "0.2.5" diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index d80a174e0f..ef373eddff 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -4,10 +4,17 @@ use crate::{ event::{EventReader, Events}, plugin::Plugin, }; -use std::{ - thread, - time::{Duration, Instant}, -}; +use std::time::Duration; + +#[cfg(not(target_arch = "wasm32"))] +use std::{thread, time::Instant}; +#[cfg(target_arch = "wasm32")] +use wasm_timer::Instant; + +#[cfg(target_arch = "wasm32")] +use std::{cell::RefCell, rc::Rc}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::{prelude::*, JsCast}; /// Determines the method used to run an [App]'s `Schedule` #[derive(Copy, Clone, Debug)] @@ -53,32 +60,74 @@ impl Plugin for ScheduleRunnerPlugin { RunMode::Once => { app.update(); } - RunMode::Loop { wait } => loop { - let start_time = Instant::now(); + RunMode::Loop { wait } => { + let mut tick = move |app: &mut App, + wait: Option| + -> Option { + let start_time = Instant::now(); - if let Some(app_exit_events) = app.resources.get_mut::>() { - if app_exit_event_reader.latest(&app_exit_events).is_some() { - break; + if let Some(app_exit_events) = app.resources.get_mut::>() { + if app_exit_event_reader.latest(&app_exit_events).is_some() { + return None; + } + } + + app.update(); + + if let Some(app_exit_events) = app.resources.get_mut::>() { + if app_exit_event_reader.latest(&app_exit_events).is_some() { + return None; + } + } + + let end_time = Instant::now(); + + if let Some(wait) = wait { + let exe_time = end_time - start_time; + if exe_time < wait { + return Some(wait - exe_time); + } + } + + None + }; + + #[cfg(not(target_arch = "wasm32"))] + { + loop { + if let Some(delay) = tick(&mut app, wait) { + thread::sleep(delay); + } } } - app.update(); - - if let Some(app_exit_events) = app.resources.get_mut::>() { - if app_exit_event_reader.latest(&app_exit_events).is_some() { - break; + #[cfg(target_arch = "wasm32")] + { + fn set_timeout(f: &Closure, dur: Duration) { + web_sys::window() + .unwrap() + .set_timeout_with_callback_and_timeout_and_arguments_0( + f.as_ref().unchecked_ref(), + dur.as_millis() as i32, + ) + .expect("should register `setTimeout`"); } - } + let asap = Duration::from_millis(1); - let end_time = Instant::now(); + let mut rc = Rc::new(app); + let f = Rc::new(RefCell::new(None)); + let g = f.clone(); - if let Some(wait) = wait { - let exe_time = end_time - start_time; - if exe_time < wait { - thread::sleep(wait - exe_time); - } - } - }, + let c = move || { + let mut app = Rc::get_mut(&mut rc).unwrap(); + let delay = tick(&mut app, wait).unwrap_or(asap); + set_timeout(f.borrow().as_ref().unwrap(), delay); + }; + + *g.borrow_mut() = Some(Closure::wrap(Box::new(c) as Box)); + set_timeout(g.borrow().as_ref().unwrap(), asap); + }; + } } }); } diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index 28ffdda055..33c379139e 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -4,9 +4,16 @@ pub use slice::{ParallelSlice, ParallelSliceMut}; mod task; pub use task::Task; +#[cfg(not(target_arch = "wasm32"))] mod task_pool; +#[cfg(not(target_arch = "wasm32"))] pub use task_pool::{Scope, TaskPool, TaskPoolBuilder}; +#[cfg(target_arch = "wasm32")] +mod single_threaded_task_pool; +#[cfg(target_arch = "wasm32")] +pub use single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder}; + mod usages; pub use usages::{AsyncComputeTaskPool, ComputeTaskPool, IOTaskPool}; diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs new file mode 100644 index 0000000000..6076b2a120 --- /dev/null +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -0,0 +1,112 @@ +use std::{ + future::Future, + mem, + pin::Pin, + sync::{Arc, Mutex}, +}; + +/// Used to create a TaskPool +#[derive(Debug, Default, Clone)] +pub struct TaskPoolBuilder {} + +impl TaskPoolBuilder { + /// Creates a new TaskPoolBuilder instance + pub fn new() -> Self { + Self::default() + } + + pub fn num_threads(self, _num_threads: usize) -> Self { + self + } + + pub fn stack_size(self, _stack_size: usize) -> Self { + self + } + + pub fn thread_name(self, _thread_name: String) -> Self { + self + } + + pub fn build(self) -> TaskPool { + TaskPool::new_internal() + } +} + +/// A thread pool for executing tasks. Tasks are futures that are being automatically driven by +/// the pool on threads owned by the pool. In this case - main thread only. +#[derive(Debug, Default, Clone)] +pub struct TaskPool {} + +impl TaskPool { + /// Create a `TaskPool` with the default configuration. + pub fn new() -> Self { + TaskPoolBuilder::new().build() + } + + #[allow(unused_variables)] + fn new_internal() -> Self { + Self {} + } + + /// Return the number of threads owned by the task pool + pub fn thread_num(&self) -> usize { + 1 + } + + /// Allows spawning non-`static futures on the thread pool. The function takes a callback, + /// passing a scope object into it. The scope object provided to the callback can be used + /// to spawn tasks. This function will await the completion of all tasks before returning. + /// + /// This is similar to `rayon::scope` and `crossbeam::scope` + pub fn scope<'scope, F, T>(&self, f: F) -> Vec + where + F: FnOnce(&mut Scope<'scope, T>) + 'scope + Send, + T: Send + 'static, + { + let executor = async_executor::LocalExecutor::new(); + + let executor: &async_executor::LocalExecutor = &executor; + let executor: &'scope async_executor::LocalExecutor = unsafe { mem::transmute(executor) }; + + let mut scope = Scope { + executor, + results: Vec::new(), + }; + + f(&mut scope); + + // Loop until all tasks are done + while executor.try_tick() {} + + scope + .results + .iter() + .map(|result| result.lock().unwrap().take().unwrap()) + .collect() + } +} + +pub struct Scope<'scope, T> { + executor: &'scope async_executor::LocalExecutor, + // Vector to gather results of all futures spawned during scope run + results: Vec>>>, +} + +impl<'scope, T: Send + 'static> Scope<'scope, T> { + pub fn spawn + 'scope + Send>(&mut self, f: Fut) { + let result = Arc::new(Mutex::new(None)); + self.results.push(result.clone()); + let f = async move { + result.lock().unwrap().replace(f.await); + }; + + // SAFETY: This function blocks until all futures complete, so we do not read/write the + // data from futures outside of the 'scope lifetime. However, rust has no way of knowing + // this so we must convert to 'static here to appease the compiler as it is unable to + // validate safety. + let fut: Pin + 'scope>> = Box::pin(f); + let fut: Pin + 'static>> = unsafe { mem::transmute(fut) }; + + self.executor.spawn(fut).detach(); + } +} diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index 059efc4d8c..f1ebd68a80 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -21,3 +21,6 @@ bevy_utils = { path = "../bevy_utils", version = "0.1" } # other uuid = { version = "0.8", features = ["v4", "serde"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +uuid = { version = "0.8", features = ["wasm-bindgen"] } diff --git a/examples/README.md b/examples/README.md index 0362862937..117e11d3b3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,7 +24,7 @@ Example | Main | Description Example | File | Description --- | --- | --- -`load_model` | [`3d/load_model.rs`](./3d/load_model.rs) | Loads and renders a simple model +`load_model` | [`3d/load_model.rs`](./3d/load_model.rs) | Loads and renders a simple model `msaa` | [`3d/msaa.rs`](./3d/msaa.rs) | Configures MSAA (Multi-Sample Anti-Aliasing) for smoother edges `parenting` | [`3d/parenting.rs`](./3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations `3d_scene` | [`3d/3d_scene.rs`](./3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting @@ -116,3 +116,19 @@ Example | File | Description `clear_color` | [`window/clear_color.rs`](./window/clear_color.rs) | Creates a solid color window `multiple_windows` | [`window/multiple_windows.rs`](./window/multiple_windows.rs) | Creates two windows and cameras viewing the same mesh `window_settings` | [`window/window_settings.rs`](./window/window_settings.rs) | Demonstrates customizing default window settings + +## WASM + +#### pre-req + + $ rustup target add wasm32-unknown-unknown + $ cargo install wasm-bindgen-cli + +#### build & run + + $ cargo build --example headless_wasm --target wasm32-unknown-unknown --no-default-features + $ wasm-bindgen --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/debug/examples/headless_wasm.wasm + +Then serve `examples/wasm` dir to browser. i.e. + + $ basic-http-server examples/wasm diff --git a/examples/wasm/.gitignore b/examples/wasm/.gitignore new file mode 100644 index 0000000000..2f7896d1d1 --- /dev/null +++ b/examples/wasm/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/examples/wasm/favicon.ico b/examples/wasm/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f2c5168a03585ac8de20ba26f7a580e5fc1de81f GIT binary patch literal 67646 zcmeI533ydSp2crA62g+mz625w1Vu#IQ6z{9D4T#Nw201t3WDN>N{=*AL|NS!9ksP_ z{aj`Q#1>oq?2h0DxH~8=jB#r~#6}QNR5HJFbL-{i`Cjt!^2iG>6~05=s#|xfbLwCJ zTJC+0lf=J*0*C+eoX#0ZPBX`GIs=lF=%#Bk)L(INv14J&z-qv1z-qv1z-qv1z-qv1 zz-qv1z-qv1z-qv1z-qv1z-qv1z-qv1z-qv1z-qv1z-qv1z-qv1z-qv1z-qv1z-XXp z)22?7CQZgSZru28@I<3Vja~rj+5P~0#P*lqD<8ffeG~DwIc_c6PZK^ucr&;PoWs6B zgh!L!iLec51nQ9P5C)+2Evr_ovg=vmxTeSB;}yc<7~?0-_ceGAyZ{~qH-gFNqCa8N zR;^mOT97W9HEZUafByMa3)QRzcn}94f>`q(>U_U(oxNZu*b3f43ya}@FkxemNm^?C z`t_|2s!0cwxA@-(Lh)6dX}h?Gzk_+;3^dUVJ=B+7AataOVZ(-5UBp-yvc2H38o<9_ zYrk+mpM#gUxB2jYd}d~*>px^?XFFZGbg7aK7A;!j+;r1T&W$(T=!V)hFlqDW&ky7= zzEUEFs#}z^{I%^sws}ABa}n=twHfjyiok8?p&wyW?mLyxIpmN-!fHXBipQBVXDUo5 z-UQ^a-Iwruun;^1o&|pw;Qx8T2g$R5@ZUgp(%OKm>#x5)nYeq-?c2A9z3#!J6Y)=- zN}*i)P@ew|W`n!HGvH(JD~PuS{MVNr-T;fiaPIvOPs8qVc~kb{2_(Z~CqnUf7kCBi z1jUua*W~{zC;%rdSg;^--n@Cv+_`gIm}?%aq3RI)%MQ3@$aWpdG#liQ)|K#F!aoyk zCRAONeXa?1LVlz)s=ntDXd#VfNz(Jclj`bNuo3J5mE}BC{{5WiJMb3v;Hn#LxS=K6 z$!sTtEeO`{`t|D_Z+yVjhWcy7+f&vTf)(I@!M}jNe#UG2XYS*9v^#;YLt0u|a<5*! zq=O^CgWwwwiqop3eakruurXa0FJA1n4Z&rYNOAe%Mh`L+lye(wf_#UWK)(Av5HJ4C zb!C@60!z_Cx2|2gI!l%;Q9FsN+6noS11Nj>Xc|k7rw(Kn@s&OS301rNT%FdfgxZyQ2)}{>FN&UxBdh41aAG5 z1~e9M-+lKb!I}Do$AHXb-tIWAbU^tZ50(MdNyAHZhVoM%0D0}&wR7OUFZc_%8$FB~IdWvZ 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 { + if state.count % 60 == 0 { + log::info!("counter system: {}", state.count); + } + state.count += 1; +} + +#[derive(Default)] +struct CounterState { + count: u32, +} diff --git a/examples/wasm/hello_wasm.rs b/examples/wasm/hello_wasm.rs new file mode 100644 index 0000000000..4354dc1ae7 --- /dev/null +++ b/examples/wasm/hello_wasm.rs @@ -0,0 +1,14 @@ +extern crate console_error_panic_hook; +use bevy::prelude::*; +use std::panic; + +fn main() { + panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init_with_level(log::Level::Debug).expect("cannot initialize console_log"); + + App::build().add_system(hello_wasm_system.system()).run(); +} + +fn hello_wasm_system() { + log::info!("hello wasm"); +} diff --git a/examples/wasm/index.html b/examples/wasm/index.html new file mode 100644 index 0000000000..929142536e --- /dev/null +++ b/examples/wasm/index.html @@ -0,0 +1,9 @@ + + + + + +