diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..758ed61 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +runner = "wasm-server-runner" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95aa3d9..04e1279 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,25 @@ jobs: run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libopenxr-loader1 libopenxr-dev - name: Run cargo test run: cargo test + - name: Install wasm libcore/libstd + if: matrix.os == 'ubuntu-latest' + run: rustup target install wasm32-unknown-unknown + - name: Install wasm-bindgen + if: matrix.os == 'ubuntu-latest' + run: cargo install wasm-bindgen-cli + - name: Build wasm + if: matrix.os == 'ubuntu-latest' + run: cargo build --release --target wasm32-unknown-unknown --bin demo && wasm-bindgen --out-dir ./web/ --target web ./target/wasm32-unknown-unknown/release/demo.wasm + - name: Push to gh-pages + if: matrix.os == 'ubuntu-latest' + uses: s0/git-publish-subdir-action@develop + env: + SQUASH_HISTORY: true + REPO: self + BRANCH: gh-pages # The branch name where you want to push the assets + FOLDER: web # The directory where your assets are generated + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub will automatically add this - you don't need to bother getting a token + MESSAGE: "Build: ({sha}) {msg}" # The commit message # Run cargo clippy -- -D warnings clippy: diff --git a/.gitignore b/.gitignore index 5e16591..27a8f36 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /assets /target /z_Usefull +/web/* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 32391d3..646393c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,3 +11,12 @@ run `x doctor` to see what else you need to install for android, then run ``` x build --release --platform android --format apk --arch arm64 ``` +## wasm + +To run the project in the browser locally, you need to do the following setup: + +```bash +rustup target install wasm32-unknown-unknown +cargo install wasm-server-runner +cargo run --target wasm32-unknown-unknown +``` diff --git a/Cargo.lock b/Cargo.lock index cc12b9e..22f48df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3320,7 +3320,11 @@ dependencies = [ "futures-core", "futures-io", "glam", + "js-sys", "surf", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 73af27b..1ea25ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,19 @@ futures-core = "0.3.29" futures-io = "0.3.29" ## bevy_config_cam = { git = "https://github.com/BlackPhlox/bevy_config_cam" } ## { version = "0.3.0"} glam = "0" -surf = { version = "2.3.2", default-features = false, features = ["h1-client-rustls"] } -[target.'cfg(not(target_os="macos"))'.dependencies] +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +surf = { version = "2.3.2", default-features = false, features = [ + "h1-client-rustls", +] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +web-sys = {version = "0.3.22", default-features = false} +js-sys = {version = "0.3", default-features = false} +wasm-bindgen = {version = "0.2", default-features = false} +wasm-bindgen-futures = "0.4" + +[target.'cfg(not(any(target_os="macos", target_arch = "wasm32")))'.dependencies] bevy_oxr = { git = "https://github.com/awtterpip/bevy_openxr", optional = true } [features] diff --git a/src/http_assets.rs b/src/http_assets.rs index b72cd0b..f25788a 100644 --- a/src/http_assets.rs +++ b/src/http_assets.rs @@ -16,6 +16,9 @@ use std::{io::Read, path::Path}; /// A custom asset reader implementation that wraps a given asset reader implementation pub struct HttpAssetReader { + #[cfg(target_arch = "wasm32")] + base_url: String, + #[cfg(not(target_arch = "wasm32"))] client: surf::Client, /// Whether to load tiles from this path tile: bool, @@ -24,16 +27,73 @@ pub struct HttpAssetReader { impl HttpAssetReader { /// Creates a new `HttpAssetReader`. The path provided will be used to build URLs to query for assets. pub fn new(base_url: &str, tile: bool) -> Self { - let base_url = surf::Url::parse(base_url).expect("invalid base url"); + #[cfg(not(target_arch = "wasm32"))] + { + let base_url = surf::Url::parse(base_url).expect("invalid base url"); - let client = surf::Config::new().set_timeout(Some(std::time::Duration::from_secs(5))); - let client = client.set_base_url(base_url); + let client = surf::Config::new().set_timeout(Some(std::time::Duration::from_secs(5))); + let client = client.set_base_url(base_url); - let client = client.try_into().expect("could not create http client"); + let client = client.try_into().expect("could not create http client"); + Self { client, tile } + } + #[cfg(target_arch = "wasm32")] + { + Self { + base_url: base_url.into(), + tile, + } + } + } + + #[cfg(target_arch = "wasm32")] + async fn fetch_bytes<'a>(&self, path: &str) -> Result>, AssetReaderError> { + use js_sys::Uint8Array; + use wasm_bindgen::JsCast; + use wasm_bindgen_futures::JsFuture; + use web_sys::Response; + + fn js_value_to_err<'a>( + context: &'a str, + ) -> impl FnOnce(wasm_bindgen::JsValue) -> std::io::Error + 'a { + move |value| { + let message = match js_sys::JSON::stringify(&value) { + Ok(js_str) => format!("Failed to {context}: {js_str}"), + Err(_) => { + format!( + "Failed to {context} and also failed to stringify the JSValue of the error" + ) + } + }; + + std::io::Error::new(std::io::ErrorKind::Other, message) + } + } - Self { client, tile } + let window = web_sys::window().unwrap(); + let resp_value = + JsFuture::from(window.fetch_with_str(&format!("{}/{path}", self.base_url))) + .await + .map_err(js_value_to_err("fetch path"))?; + let resp = resp_value + .dyn_into::() + .map_err(js_value_to_err("convert fetch to Response"))?; + match resp.status() { + 200 => { + let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap(); + let bytes = Uint8Array::new(&data).to_vec(); + let reader: Box = Box::new(VecReader::new(bytes)); + Ok(reader) + } + 404 => Err(AssetReaderError::NotFound(path.into())), + status => Err(AssetReaderError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Encountered unexpected HTTP status {status}"), + ))), + } } + #[cfg(not(target_arch = "wasm32"))] async fn fetch_bytes<'a>(&self, path: &str) -> Result>, AssetReaderError> { let resp = self.client.get(path).await; diff --git a/src/lib.rs b/src/lib.rs index 1c48801..46096c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Loads and renders a glTF file as a scene. use bevy::prelude::*; -#[cfg(all(feature = "xr", not(target_os = "macos")))] +#[cfg(all(feature = "xr", not(any(target_os = "macos", target_arch = "wasm32"))))] use bevy_oxr::xr_input::trackers::OpenXRTrackingRoot; use http_assets::HttpAssetReaderPlugin; @@ -11,18 +11,17 @@ mod flycam; mod http_assets; mod sun; mod tilemap; -#[cfg(all(feature = "xr", not(target_os = "macos")))] +#[cfg(all(feature = "xr", not(any(target_os = "macos", target_arch = "wasm32"))))] mod xr; #[bevy_main] pub fn main() { - std::env::set_var("RUST_BACKTRACE", "1"); let mut app = App::new(); app.add_plugins(HttpAssetReaderPlugin { base_url: "https://gltiles.osm2world.org/glb/".into(), }); if std::env::args().any(|arg| arg == "xr") { - #[cfg(all(feature = "xr", not(target_os = "macos")))] + #[cfg(all(feature = "xr", not(any(target_os = "macos", target_arch = "wasm32"))))] app.add_plugins(xr::Plugin); } else { app.add_plugins(DefaultPlugins); @@ -59,7 +58,7 @@ fn setup(mut commands: Commands, mut meshes: ResMut>) { /// Used to make sure all players have a map to walk on. pub struct LocalPlayer; -#[cfg(not(all(feature = "xr", not(target_os = "macos"))))] +#[cfg(not(all(feature = "xr", not(any(target_os = "macos", target_arch = "wasm32")))))] /// HACK: we can't attach `LocalPlayer` to the xr player yet, so we need /// to access the OpenXRTrackingRoot, but that doesn't exist without the xr feature type OpenXRTrackingRoot = LocalPlayer; diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..86631a6 --- /dev/null +++ b/web/index.html @@ -0,0 +1,30 @@ + + + + + OSMeta + + + + + +