From 05bf8aeb3a48abdf3e17c014dbabaed28833293f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20Kry=C5=84ski?= Date: Mon, 19 Oct 2020 18:46:47 +0200 Subject: [PATCH] wasm assets --- crates/bevy_asset/Cargo.toml | 9 +- crates/bevy_asset/src/asset_server.rs | 20 +++-- crates/bevy_asset/src/io.rs | 17 ++-- crates/bevy_asset/src/lib.rs | 6 ++ crates/bevy_asset/src/loader.rs | 9 +- crates/bevy_asset/src/wasm_io.rs | 109 +++++++++++++++++++++++++ crates/bevy_gltf/Cargo.toml | 1 + crates/bevy_gltf/src/loader.rs | 17 ++-- crates/bevy_winit/src/winit_windows.rs | 8 +- 9 files changed, 171 insertions(+), 25 deletions(-) create mode 100644 crates/bevy_asset/src/wasm_io.rs diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 4bdc7a8b93b7d0..e0ec039963aa2b 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -13,7 +13,7 @@ license = "MIT" keywords = ["bevy"] [features] -default = ["filesystem_watcher"] +default = [] filesystem_watcher = ["notify"] [dependencies] @@ -37,3 +37,10 @@ log = { version = "0.4", features = ["release_max_level_info"] } notify = { version = "5.0.0-pre.2", optional = true } parking_lot = "0.11.0" rand = "0.7.3" +async-trait = "0.1.41" + +web-sys = { version = "0.3", features = ["Request", "Window", "Response"]} +wasm-bindgen = "0.2.68" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +futures-lite = "1.11.1" diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index fb62719a3dbf8a..cf1f70fa5ad1a9 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -29,13 +29,19 @@ pub enum AssetServerError { PathLoaderError(#[from] AssetIoError), } +#[cfg(not(target_arch="wasm32"))] +type AssetIoImpl = FileAssetIo; + +#[cfg(target_arch="wasm32")] +type AssetIoImpl = crate::wasm_io::WebAssetIo; + #[derive(Default)] pub(crate) struct AssetRefCounter { pub(crate) channel: Arc, pub(crate) ref_counts: Arc>>, } -pub struct AssetServerInternal { +pub struct AssetServerInternal { pub(crate) asset_io: TAssetIo, pub(crate) asset_ref_counter: AssetRefCounter, pub(crate) asset_sources: Arc>>, @@ -46,8 +52,9 @@ pub struct AssetServerInternal { task_pool: TaskPool, } + /// Loads assets from the filesystem on background threads -pub struct AssetServer { +pub struct AssetServer { pub(crate) server: Arc>, } @@ -180,7 +187,7 @@ impl AssetServer { } // TODO: properly set failed LoadState in all failure cases - fn load_sync<'a, P: Into>>( + async fn load_async<'a, P: Into>>( &self, path: P, force: bool, @@ -221,7 +228,7 @@ impl AssetServer { }; // load the asset bytes - let bytes = self.server.asset_io.load_path(asset_path.path())?; + let bytes = self.server.asset_io.load_path(asset_path.path()).await?; // load the asset source using the corresponding AssetLoader let mut load_context = LoadContext::new( @@ -231,7 +238,8 @@ impl AssetServer { version, ); asset_loader - .load(&bytes, &mut load_context) + .load_async(&bytes, &mut load_context) + .await .map_err(AssetServerError::AssetLoaderError)?; // if version has changed since we loaded and grabbed a lock, return. theres is a newer version being loaded @@ -291,7 +299,7 @@ impl AssetServer { self.server .task_pool .spawn(async move { - server.load_sync(owned_path, force).unwrap(); + server.load_async(owned_path, force).await.unwrap(); }) .detach(); asset_path.into() diff --git a/crates/bevy_asset/src/io.rs b/crates/bevy_asset/src/io.rs index aa44d35dd16f75..ba1a293cb0faa8 100644 --- a/crates/bevy_asset/src/io.rs +++ b/crates/bevy_asset/src/io.rs @@ -11,8 +11,10 @@ use std::{ sync::Arc, }; use thiserror::Error; - -use crate::{filesystem_watcher::FilesystemWatcher, AssetServer}; +#[cfg(feature = "filesystem_watcher")] +use crate::{filesystem_watcher::FilesystemWatcher}; +use crate::AssetServer; +use async_trait::async_trait; /// Errors that occur while loading assets #[derive(Error, Debug)] @@ -26,9 +28,10 @@ pub enum AssetIoError { } /// Handles load requests from an AssetServer +#[async_trait] pub trait AssetIo: Send + Sync + 'static { - fn load_path(&self, path: &Path) -> Result, AssetIoError>; - fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError>; + async fn load_path(&self, path: &Path) -> Result, AssetIoError>; + async fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError>; fn read_directory( &self, path: &Path, @@ -47,6 +50,7 @@ pub struct FileAssetIo { impl FileAssetIo { pub fn new>(path: P) -> Self { FileAssetIo { + #[cfg(feature = "filesystem_watcher")] filesystem_watcher: Default::default(), root_path: Self::get_root_path().join(path.as_ref()), } @@ -67,8 +71,9 @@ impl FileAssetIo { } } +#[async_trait] impl AssetIo for FileAssetIo { - fn load_path(&self, path: &Path) -> Result, AssetIoError> { + async fn load_path(&self, path: &Path) -> Result, AssetIoError> { let mut bytes = Vec::new(); match File::open(self.root_path.join(path)) { Ok(mut file) => { @@ -98,7 +103,7 @@ impl AssetIo for FileAssetIo { ))) } - fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError> { + async fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError> { let path = self.root_path.join(path); if let Some(parent_path) = path.parent() { fs::create_dir_all(parent_path)?; diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 322e651517cc7f..c5c9f3b18050db 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -7,6 +7,7 @@ mod info; mod io; mod loader; mod path; +pub mod wasm_io; pub use asset_server::*; pub use assets::*; @@ -61,7 +62,12 @@ impl Plugin for AssetPlugin { let settings = app .resources_mut() .get_or_insert_with(AssetServerSettings::default); + + #[cfg(not(target_arch="wasm32"))] let source = FileAssetIo::new(&settings.asset_folder); + #[cfg(target_arch="wasm32")] + let source = wasm_io::WebAssetIo::new(&settings.asset_folder); + AssetServer::new(source, task_pool) }; diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 5d92acc0416b7c..b5e68497cb1154 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -9,9 +9,14 @@ use bevy_utils::HashMap; use crossbeam_channel::{Receiver, Sender}; use downcast_rs::{impl_downcast, Downcast}; use std::path::Path; +use async_trait::async_trait; /// A loader for an asset source +#[async_trait] pub trait AssetLoader: Send + Sync + 'static { + async fn load_async<'a>(&self, bytes: &[u8], load_context: &mut LoadContext<'a>) -> Result<(), anyhow::Error> { + self.load(bytes, load_context) + } fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<(), anyhow::Error>; fn extensions(&self) -> &[&str]; } @@ -94,8 +99,8 @@ impl<'a> LoadContext<'a> { Handle::strong(id.into(), self.ref_change_channel.sender.clone()) } - pub fn read_asset_bytes>(&self, path: P) -> Result, AssetIoError> { - self.asset_io.load_path(path.as_ref()) + pub async fn read_asset_bytes>(&self, path: P) -> Result, AssetIoError> { + self.asset_io.load_path(path.as_ref()).await } pub fn get_asset_metas(&self) -> Vec { diff --git a/crates/bevy_asset/src/wasm_io.rs b/crates/bevy_asset/src/wasm_io.rs new file mode 100644 index 00000000000000..e4bf1fb058956d --- /dev/null +++ b/crates/bevy_asset/src/wasm_io.rs @@ -0,0 +1,109 @@ +use crate::io::{AssetIo, AssetIoError}; +use anyhow::Result; +use async_trait::async_trait; +use std::{ + path::{Path, PathBuf}, +}; + +use js_sys::Uint8Array; +use wasm_bindgen::{JsCast, JsValue}; +use wasm_bindgen_futures::JsFuture; +use web_sys::Response; + +pub struct WebAssetIo { + pub root_path: PathBuf, +} + +impl WebAssetIo { + pub fn new>(path: P) -> Self { + WebAssetIo { + root_path: path.as_ref().into() + } + } +} + +#[derive(Debug)] +struct SafeJsFuture(JsFuture); + +impl SafeJsFuture { + pub fn new(js_future: JsFuture) -> SafeJsFuture { + SafeJsFuture(js_future) + } +} + +unsafe impl Send for SafeJsFuture {} + +#[derive(Debug)] +pub struct SafeJsValue(JsValue); +unsafe impl Send for SafeJsValue {} + +impl SafeJsValue { + pub fn dyn_into(self) -> std::result::Result + where + T: JsCast, + { + self.0.dyn_into() + } +} + +use futures_lite::FutureExt; + +impl std::future::Future for SafeJsFuture { + type Output = Result; + + fn poll( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + let ret = self.0.poll(cx); + ret.map(|r| r.map(|f| SafeJsValue(f)).map_err(|f| SafeJsValue(f))) + } +} + +#[async_trait] +impl AssetIo for WebAssetIo { + async fn load_path(&self, path: &Path) -> Result, AssetIoError> { + log::info!("root_path: {:?} path: {:?}", self.root_path, path); + let path = self.root_path.join(Path::new(path)); + log::info!("path: {:?}", path); + let resp_value = SafeJsFuture::new(JsFuture::from( + web_sys::window() + .unwrap() + .fetch_with_str(path.to_str().unwrap()) + )); + let response_data = SafeJsFuture::new(JsFuture::from( + resp_value + .await + .unwrap() + .dyn_into::() + .unwrap() + .array_buffer() + .unwrap(), + )); + let data = response_data.await.unwrap(); + Ok(Uint8Array::new(&data.0).to_vec()) + } + + fn read_directory( + &self, + _path: &Path, + ) -> Result>, AssetIoError> { + panic!("not supported on this platform"); + } + + async fn save_path(&self, _path: &Path, _bytes: &[u8]) -> Result<(), AssetIoError> { + panic!("not supported on this platform"); + } + + fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> { + Ok(()) + } + + fn watch_for_changes(&self) -> Result<(), AssetIoError> { + Ok(()) + } + + fn is_directory(&self, _path: &Path) -> bool { + false + } +} diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 3d97bd1ad70147..2e168332dc3cea 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -30,3 +30,4 @@ image = { version = "0.23", default-features = false } thiserror = "1.0" anyhow = "1.0" base64 = "0.12.3" +async-trait = "0.1.41" \ No newline at end of file diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 3587c40a80aa3f..8d180d8bc35700 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -18,6 +18,7 @@ use gltf::{mesh::Mode, Primitive}; use image::{GenericImageView, ImageFormat}; use std::path::Path; use thiserror::Error; +use async_trait::async_trait; /// An error that occurs when loading a GLTF file #[derive(Error, Debug)] @@ -46,9 +47,13 @@ pub enum GltfError { #[derive(Default)] pub struct GltfLoader; +#[async_trait] impl AssetLoader for GltfLoader { + async fn load_async<'a>(&self, bytes: &[u8], load_context: &mut LoadContext<'a>) -> Result<()> { + Ok(load_gltf(bytes, load_context).await?) + } fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { - Ok(load_gltf(bytes, load_context)?) + panic!("use async_load") } fn extensions(&self) -> &[&str] { @@ -57,10 +62,10 @@ impl AssetLoader for GltfLoader { } } -fn load_gltf(bytes: &[u8], load_context: &mut LoadContext) -> Result<(), GltfError> { +async fn load_gltf<'a>(bytes: &[u8], load_context: &mut LoadContext<'a>) -> Result<(), GltfError> { let gltf = gltf::Gltf::from_slice(bytes)?; let mut world = World::default(); - let buffer_data = load_buffers(&gltf, load_context, load_context.path())?; + let buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?; let world_builder = &mut world.build(); @@ -267,9 +272,9 @@ fn get_primitive_topology(mode: Mode) -> Result { } } -fn load_buffers( +async fn load_buffers<'a>( gltf: &gltf::Gltf, - load_context: &LoadContext, + load_context: &LoadContext<'a>, asset_path: &Path, ) -> Result>, GltfError> { const OCTET_STREAM_URI: &str = "data:application/octet-stream;base64,"; @@ -287,7 +292,7 @@ fn load_buffers( } else { // TODO: Remove this and add dep let buffer_path = asset_path.parent().unwrap().join(uri); - let buffer_bytes = load_context.read_asset_bytes(buffer_path)?; + let buffer_bytes = load_context.read_asset_bytes(buffer_path).await?; buffer_data.push(buffer_bytes); } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 774f624f468312..35722895ea1de1 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -71,10 +71,10 @@ impl WinitWindows { let winit_window = winit_window_builder.build(&event_loop).unwrap(); - winit_window - .set_cursor_grab(window.cursor_locked()) - .unwrap(); - winit_window.set_cursor_visible(window.cursor_visible()); + // winit_window + // .set_cursor_grab(window.cursor_locked()) + // .unwrap(); + // winit_window.set_cursor_visible(window.cursor_visible()); self.window_id_to_winit .insert(window.id(), winit_window.id());