diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 4bdc7a8b93b7d..5352a798ed45b 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -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.40" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { version = "0.2" } +web-sys = { version = "0.3", features = ["Request", "Window", "Response"]} +wasm-bindgen-futures = "0.4" +js-sys = "0.3" \ No newline at end of file diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index fb62719a3dbf8..e40ca0b88a984 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -1,8 +1,8 @@ use crate::{ path::{AssetPath, AssetPathId, SourcePathId}, Asset, AssetIo, AssetIoError, AssetLifecycle, AssetLifecycleChannel, AssetLifecycleEvent, - AssetLoader, Assets, FileAssetIo, Handle, HandleId, HandleUntyped, LabelId, LoadContext, - LoadState, RefChange, RefChangeChannel, SourceInfo, SourceMeta, + AssetLoader, Assets, Handle, HandleId, HandleUntyped, LabelId, LoadContext, LoadState, + RefChange, RefChangeChannel, SourceInfo, SourceMeta, }; use anyhow::Result; use bevy_ecs::Res; @@ -35,8 +35,8 @@ pub(crate) struct AssetRefCounter { pub(crate) ref_counts: Arc>>, } -pub struct AssetServerInternal { - pub(crate) asset_io: TAssetIo, +pub struct AssetServerInternal { + pub(crate) asset_io: Box, pub(crate) asset_ref_counter: AssetRefCounter, pub(crate) asset_sources: Arc>>, pub(crate) asset_lifecycles: Arc>>>, @@ -47,11 +47,11 @@ pub struct AssetServerInternal { } /// Loads assets from the filesystem on background threads -pub struct AssetServer { - pub(crate) server: Arc>, +pub struct AssetServer { + pub(crate) server: Arc, } -impl Clone for AssetServer { +impl Clone for AssetServer { fn clone(&self) -> Self { Self { server: self.server.clone(), @@ -59,8 +59,8 @@ impl Clone for AssetServer { } } -impl AssetServer { - pub fn new(source_io: TAssetIo, task_pool: TaskPool) -> Self { +impl AssetServer { + pub fn new(source_io: T, task_pool: TaskPool) -> Self { AssetServer { server: Arc::new(AssetServerInternal { loaders: Default::default(), @@ -70,7 +70,7 @@ impl AssetServer { handle_to_path: Default::default(), asset_lifecycles: Default::default(), task_pool, - asset_io: source_io, + asset_io: Box::new(source_io), }), } } @@ -180,7 +180,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,17 +221,18 @@ 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( asset_path.path(), &self.server.asset_ref_counter.channel, - &self.server.asset_io, + &*self.server.asset_io, version, ); asset_loader .load(&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 +292,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/file_asset_io.rs similarity index 70% rename from crates/bevy_asset/src/io.rs rename to crates/bevy_asset/src/io/file_asset_io.rs index aa44d35dd16f7..cda69be745142 100644 --- a/crates/bevy_asset/src/io.rs +++ b/crates/bevy_asset/src/io/file_asset_io.rs @@ -1,4 +1,6 @@ +use crate::{filesystem_watcher::FilesystemWatcher, AssetIo, AssetIoError, AssetServer}; use anyhow::Result; +use async_trait::async_trait; use bevy_ecs::Res; use bevy_utils::HashSet; use crossbeam_channel::TryRecvError; @@ -10,33 +12,6 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use thiserror::Error; - -use crate::{filesystem_watcher::FilesystemWatcher, AssetServer}; - -/// Errors that occur while loading assets -#[derive(Error, Debug)] -pub enum AssetIoError { - #[error("Path not found")] - NotFound(PathBuf), - #[error("Encountered an io error while loading asset.")] - Io(#[from] io::Error), - #[error("Failed to watch path")] - PathWatchError(PathBuf), -} - -/// Handles load requests from an AssetServer -pub trait AssetIo: Send + Sync + 'static { - fn load_path(&self, path: &Path) -> Result, AssetIoError>; - fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError>; - fn read_directory( - &self, - path: &Path, - ) -> Result>, AssetIoError>; - fn is_directory(&self, path: &Path) -> bool; - fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError>; - fn watch_for_changes(&self) -> Result<(), AssetIoError>; -} pub struct FileAssetIo { root_path: PathBuf, @@ -67,8 +42,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,15 +74,6 @@ impl AssetIo for FileAssetIo { ))) } - 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)?; - } - - Ok(fs::write(self.root_path.join(path), bytes)?) - } - fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError> { #[cfg(feature = "filesystem_watcher")] { @@ -139,7 +106,13 @@ impl AssetIo for FileAssetIo { #[cfg(feature = "filesystem_watcher")] pub fn filesystem_watcher_system(asset_server: Res) { let mut changed = HashSet::default(); - let watcher = asset_server.server.asset_io.filesystem_watcher.read(); + let asset_io = + if let Some(asset_io) = asset_server.server.asset_io.downcast_ref::() { + asset_io + } else { + return; + }; + let watcher = asset_io.filesystem_watcher.read(); if let Some(ref watcher) = *watcher { loop { let event = match watcher.receiver.try_recv() { @@ -155,9 +128,7 @@ pub fn filesystem_watcher_system(asset_server: Res) { { for path in paths.iter() { if !changed.contains(path) { - let relative_path = path - .strip_prefix(&asset_server.server.asset_io.root_path) - .unwrap(); + let relative_path = path.strip_prefix(&asset_io.root_path).unwrap(); let _ = asset_server.load_untracked(relative_path, true); } } diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs new file mode 100644 index 0000000000000..156bb24b1a2a8 --- /dev/null +++ b/crates/bevy_asset/src/io/mod.rs @@ -0,0 +1,45 @@ +#[cfg(not(target_arch = "wasm32"))] +mod file_asset_io; +#[cfg(target_arch = "wasm32")] +mod wasm_asset_io; + +#[cfg(not(target_arch = "wasm32"))] +pub use file_asset_io::*; +#[cfg(target_arch = "wasm32")] +pub use wasm_asset_io::*; + +use anyhow::Result; +use async_trait::async_trait; +use downcast_rs::{impl_downcast, Downcast}; +use std::{ + io, + path::{Path, PathBuf}, +}; +use thiserror::Error; + +/// Errors that occur while loading assets +#[derive(Error, Debug)] +pub enum AssetIoError { + #[error("Path not found")] + NotFound(PathBuf), + #[error("Encountered an io error while loading asset.")] + Io(#[from] io::Error), + #[error("Failed to watch path")] + PathWatchError(PathBuf), +} + +/// Handles load requests from an AssetServer +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait AssetIo: Downcast + Send + Sync + 'static { + async fn load_path(&self, path: &Path) -> Result, AssetIoError>; + fn read_directory( + &self, + path: &Path, + ) -> Result>, AssetIoError>; + fn is_directory(&self, path: &Path) -> bool; + fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError>; + fn watch_for_changes(&self) -> Result<(), AssetIoError>; +} + +impl_downcast!(AssetIo); diff --git a/crates/bevy_asset/src/io/wasm_asset_io.rs b/crates/bevy_asset/src/io/wasm_asset_io.rs new file mode 100644 index 0000000000000..86d490b328f10 --- /dev/null +++ b/crates/bevy_asset/src/io/wasm_asset_io.rs @@ -0,0 +1,54 @@ +use crate::{AssetIo, AssetIoError}; +use anyhow::Result; +use async_trait::async_trait; +use js_sys::Uint8Array; +use std::path::{Path, PathBuf}; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::JsFuture; +use web_sys::Response; + +pub struct WasmAssetIo { + root_path: PathBuf, +} + +impl WasmAssetIo { + pub fn new>(path: P) -> Self { + WasmAssetIo { + root_path: path.as_ref().to_owned(), + } + } +} + +#[async_trait(?Send)] +impl AssetIo for WasmAssetIo { + async fn load_path(&self, path: &Path) -> Result, AssetIoError> { + let path = self.root_path.join(path); + let window = web_sys::window().unwrap(); + let resp_value = JsFuture::from(window.fetch_with_str(path.to_str().unwrap())) + .await + .unwrap(); + let resp: Response = resp_value.dyn_into().unwrap(); + let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap(); + let bytes = Uint8Array::new(&data).to_vec(); + Ok(bytes) + } + + fn read_directory( + &self, + _path: &Path, + ) -> Result>, AssetIoError> { + Ok(Box::new(std::iter::empty::())) + } + + 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 { + self.root_path.join(path).is_dir() + } +} diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 322e651517cc7..e62748f838abc 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -1,6 +1,6 @@ mod asset_server; mod assets; -#[cfg(feature = "filesystem_watcher")] +#[cfg(all(feature = "filesystem_watcher", not(target_arch = "wasm32")))] mod filesystem_watcher; mod handle; mod info; @@ -37,7 +37,7 @@ use bevy_type_registry::RegisterType; pub struct AssetPlugin; pub struct AssetServerSettings { - asset_folder: String, + pub asset_folder: String, } impl Default for AssetServerSettings { @@ -61,7 +61,11 @@ 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 = WasmAssetIo::new(&settings.asset_folder); AssetServer::new(source, task_pool) }; @@ -74,7 +78,7 @@ impl Plugin for AssetPlugin { asset_server::free_unused_assets_system.system(), ); - #[cfg(feature = "filesystem_watcher")] + #[cfg(all(feature = "filesystem_watcher", not(target_arch = "wasm32")))] app.add_system_to_stage(stage::LOAD_ASSETS, io::filesystem_watcher_system.system()); } } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 5d92acc0416b7..2029d37ab67d6 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -5,14 +5,18 @@ use crate::{ use anyhow::Result; use bevy_ecs::{Res, ResMut, Resource}; use bevy_type_registry::{TypeUuid, TypeUuidDynamic}; -use bevy_utils::HashMap; +use bevy_utils::{BoxedFuture, HashMap}; use crossbeam_channel::{Receiver, Sender}; use downcast_rs::{impl_downcast, Downcast}; use std::path::Path; /// A loader for an asset source pub trait AssetLoader: Send + Sync + 'static { - fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<(), anyhow::Error>; + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result<(), anyhow::Error>>; fn extensions(&self) -> &[&str]; } @@ -94,8 +98,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_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index 7ab6292c77d86..c8a9e480b31a0 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -18,6 +18,7 @@ bevy_app = { path = "../bevy_app", version = "0.2.1" } bevy_asset = { path = "../bevy_asset", version = "0.2.1" } bevy_ecs = { path = "../bevy_ecs", version = "0.2.1" } bevy_type_registry = { path = "../bevy_type_registry", version = "0.2.1" } +bevy_utils = { path = "../bevy_utils", version = "0.2.1" } # other anyhow = "1.0" diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 2dd852d480546..d1ea5273940f9 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -1,6 +1,7 @@ use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_type_registry::TypeUuid; +use bevy_utils::BoxedFuture; use std::{io::Cursor, sync::Arc}; /// A source of audio data @@ -21,11 +22,11 @@ impl AsRef<[u8]> for AudioSource { pub struct Mp3Loader; impl AssetLoader for Mp3Loader { - fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { + fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> BoxedFuture> { load_context.set_default_asset(LoadedAsset::new(AudioSource { bytes: bytes.into(), })); - Ok(()) + Box::pin(async move { Ok(()) }) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 3587c40a80aa3..b82ee33c76f3e 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -1,6 +1,6 @@ use anyhow::Result; use bevy_asset::{AssetIoError, AssetLoader, AssetPath, LoadContext, LoadedAsset}; -use bevy_ecs::{World, WorldBuilderSource}; +use bevy_ecs::{bevy_utils::BoxedFuture, World, WorldBuilderSource}; use bevy_math::Mat4; use bevy_pbr::prelude::{PbrComponents, StandardMaterial}; use bevy_render::{ @@ -47,8 +47,12 @@ pub enum GltfError { pub struct GltfLoader; impl AssetLoader for GltfLoader { - fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { - Ok(load_gltf(bytes, load_context)?) + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result<()>> { + Box::pin(async move { Ok(load_gltf(bytes, load_context).await?) }) } fn extensions(&self) -> &[&str] { @@ -57,10 +61,13 @@ impl AssetLoader for GltfLoader { } } -fn load_gltf(bytes: &[u8], load_context: &mut LoadContext) -> Result<(), GltfError> { +async fn load_gltf<'a, 'b>( + bytes: &'a [u8], + load_context: &'a mut LoadContext<'b>, +) -> 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 +274,9 @@ fn get_primitive_topology(mode: Mode) -> Result { } } -fn load_buffers( +async fn load_buffers( gltf: &gltf::Gltf, - load_context: &LoadContext, + load_context: &LoadContext<'_>, asset_path: &Path, ) -> Result>, GltfError> { const OCTET_STREAM_URI: &str = "data:application/octet-stream;base64,"; @@ -287,7 +294,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_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs index 2c3bbc3c2d8f7..42b341608f000 100644 --- a/crates/bevy_render/src/texture/hdr_texture_loader.rs +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -2,42 +2,49 @@ use super::{Texture, TextureFormat}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_math::Vec2; +use bevy_utils::BoxedFuture; /// Loads HDR textures as Texture assets #[derive(Clone, Default)] pub struct HdrTextureLoader; impl AssetLoader for HdrTextureLoader { - fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { - let format = TextureFormat::Rgba32Float; - debug_assert_eq!( - format.pixel_size(), - 4 * 4, - "Format should have 32bit x 4 size" - ); - - let decoder = image::hdr::HdrDecoder::new(bytes)?; - let info = decoder.metadata(); - let rgb_data = decoder.read_image_hdr()?; - let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size()); - - for rgb in rgb_data { - let alpha = 1.0f32; - - rgba_data.extend_from_slice(&rgb.0[0].to_ne_bytes()); - rgba_data.extend_from_slice(&rgb.0[1].to_ne_bytes()); - rgba_data.extend_from_slice(&rgb.0[2].to_ne_bytes()); - rgba_data.extend_from_slice(&alpha.to_ne_bytes()); - } - - let texture = Texture::new( - Vec2::new(info.width as f32, info.height as f32), - rgba_data, - format, - ); - - load_context.set_default_asset(LoadedAsset::new(texture)); - Ok(()) + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result<()>> { + Box::pin(async move { + let format = TextureFormat::Rgba32Float; + debug_assert_eq!( + format.pixel_size(), + 4 * 4, + "Format should have 32bit x 4 size" + ); + + let decoder = image::hdr::HdrDecoder::new(bytes)?; + let info = decoder.metadata(); + let rgb_data = decoder.read_image_hdr()?; + let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size()); + + for rgb in rgb_data { + let alpha = 1.0f32; + + rgba_data.extend_from_slice(&rgb.0[0].to_ne_bytes()); + rgba_data.extend_from_slice(&rgb.0[1].to_ne_bytes()); + rgba_data.extend_from_slice(&rgb.0[2].to_ne_bytes()); + rgba_data.extend_from_slice(&alpha.to_ne_bytes()); + } + + let texture = Texture::new( + Vec2::new(info.width as f32, info.height as f32), + rgba_data, + format, + ); + + load_context.set_default_asset(LoadedAsset::new(texture)); + Ok(()) + }) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_render/src/texture/image_texture_loader.rs b/crates/bevy_render/src/texture/image_texture_loader.rs index 6f6f10032378e..b0e8f00d97658 100644 --- a/crates/bevy_render/src/texture/image_texture_loader.rs +++ b/crates/bevy_render/src/texture/image_texture_loader.rs @@ -2,6 +2,7 @@ use super::{Texture, TextureFormat}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_math::Vec2; +use bevy_utils::BoxedFuture; /// Loader for images that can be read by the `image` crate. /// @@ -10,141 +11,147 @@ use bevy_math::Vec2; pub struct ImageTextureLoader; impl AssetLoader for ImageTextureLoader { - fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { - use bevy_core::AsBytes; - - // Find the image type we expect. A file with the extension "png" should - // probably load as a PNG. - - let ext = load_context.path().extension().unwrap().to_str().unwrap(); - - // NOTE: If more formats are added they can be added here. - let img_format = if ext.eq_ignore_ascii_case("png") { - image::ImageFormat::Png - } else { - panic!( - "Unexpected image format {:?} for file {}, this is an error in `bevy_render`.", - ext, - load_context.path().display() - ) - }; - - // Load the image in the expected format. - // Some formats like PNG allow for R or RG textures too, so the texture - // format needs to be determined. For RGB textures an alpha channel - // needs to be added, so the image data needs to be converted in those - // cases. - - let dyn_img = image::load_from_memory_with_format(bytes, img_format)?; - - let width; - let height; - - let data: Vec; - let format: TextureFormat; - - match dyn_img { - image::DynamicImage::ImageLuma8(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::R8Unorm; - - data = i.into_raw(); - } - image::DynamicImage::ImageLumaA8(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::Rg8Unorm; - - data = i.into_raw(); - } - image::DynamicImage::ImageRgb8(i) => { - let i = image::DynamicImage::ImageRgb8(i).into_rgba(); - width = i.width(); - height = i.height(); - format = TextureFormat::Rgba8UnormSrgb; + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result<()>> { + Box::pin(async move { + use bevy_core::AsBytes; + + // Find the image type we expect. A file with the extension "png" should + // probably load as a PNG. + + let ext = load_context.path().extension().unwrap().to_str().unwrap(); + + // NOTE: If more formats are added they can be added here. + let img_format = if ext.eq_ignore_ascii_case("png") { + image::ImageFormat::Png + } else { + panic!( + "Unexpected image format {:?} for file {}, this is an error in `bevy_render`.", + ext, + load_context.path().display() + ) + }; + + // Load the image in the expected format. + // Some formats like PNG allow for R or RG textures too, so the texture + // format needs to be determined. For RGB textures an alpha channel + // needs to be added, so the image data needs to be converted in those + // cases. + + let dyn_img = image::load_from_memory_with_format(bytes, img_format)?; + + let width; + let height; + + let data: Vec; + let format: TextureFormat; + + match dyn_img { + image::DynamicImage::ImageLuma8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::R8Unorm; + + data = i.into_raw(); + } + image::DynamicImage::ImageLumaA8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rg8Unorm; - data = i.into_raw(); - } - image::DynamicImage::ImageRgba8(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::Rgba8UnormSrgb; + data = i.into_raw(); + } + image::DynamicImage::ImageRgb8(i) => { + let i = image::DynamicImage::ImageRgb8(i).into_rgba(); + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; - data = i.into_raw(); - } - image::DynamicImage::ImageBgr8(i) => { - let i = image::DynamicImage::ImageBgr8(i).into_bgra(); + data = i.into_raw(); + } + image::DynamicImage::ImageRgba8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; - width = i.width(); - height = i.height(); - format = TextureFormat::Bgra8UnormSrgb; + data = i.into_raw(); + } + image::DynamicImage::ImageBgr8(i) => { + let i = image::DynamicImage::ImageBgr8(i).into_bgra(); - data = i.into_raw(); - } - image::DynamicImage::ImageBgra8(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::Bgra8UnormSrgb; + width = i.width(); + height = i.height(); + format = TextureFormat::Bgra8UnormSrgb; - data = i.into_raw(); - } - image::DynamicImage::ImageLuma16(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::R16Uint; + data = i.into_raw(); + } + image::DynamicImage::ImageBgra8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Bgra8UnormSrgb; - let raw_data = i.into_raw(); + data = i.into_raw(); + } + image::DynamicImage::ImageLuma16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::R16Uint; - data = raw_data.as_slice().as_bytes().to_owned(); - } - image::DynamicImage::ImageLumaA16(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::Rg16Uint; + let raw_data = i.into_raw(); - let raw_data = i.into_raw(); + data = raw_data.as_slice().as_bytes().to_owned(); + } + image::DynamicImage::ImageLumaA16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rg16Uint; - data = raw_data.as_slice().as_bytes().to_owned(); - } + let raw_data = i.into_raw(); - image::DynamicImage::ImageRgb16(image) => { - width = image.width(); - height = image.height(); - format = TextureFormat::Rgba16Uint; - - let mut local_data = - Vec::with_capacity(width as usize * height as usize * format.pixel_size()); - - for pixel in image.into_raw().chunks_exact(3) { - // TODO unsafe_get in release builds? - let r = pixel[0]; - let g = pixel[1]; - let b = pixel[2]; - let a = u16::max_value(); - - local_data.extend_from_slice(&r.to_ne_bytes()); - local_data.extend_from_slice(&g.to_ne_bytes()); - local_data.extend_from_slice(&b.to_ne_bytes()); - local_data.extend_from_slice(&a.to_ne_bytes()); + data = raw_data.as_slice().as_bytes().to_owned(); } - data = local_data; - } - image::DynamicImage::ImageRgba16(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::Rgba16Uint; + image::DynamicImage::ImageRgb16(image) => { + width = image.width(); + height = image.height(); + format = TextureFormat::Rgba16Uint; + + let mut local_data = + Vec::with_capacity(width as usize * height as usize * format.pixel_size()); - let raw_data = i.into_raw(); + for pixel in image.into_raw().chunks_exact(3) { + // TODO unsafe_get in release builds? + let r = pixel[0]; + let g = pixel[1]; + let b = pixel[2]; + let a = u16::max_value(); - data = raw_data.as_slice().as_bytes().to_owned(); + local_data.extend_from_slice(&r.to_ne_bytes()); + local_data.extend_from_slice(&g.to_ne_bytes()); + local_data.extend_from_slice(&b.to_ne_bytes()); + local_data.extend_from_slice(&a.to_ne_bytes()); + } + + data = local_data; + } + image::DynamicImage::ImageRgba16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba16Uint; + + let raw_data = i.into_raw(); + + data = raw_data.as_slice().as_bytes().to_owned(); + } } - } - let texture = Texture::new(Vec2::new(width as f32, height as f32), data, format); - load_context.set_default_asset(LoadedAsset::new(texture)); - Ok(()) + let texture = Texture::new(Vec2::new(width as f32, height as f32), data, format); + load_context.set_default_asset(LoadedAsset::new(texture)); + Ok(()) + }) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_scene/src/scene_loader.rs b/crates/bevy_scene/src/scene_loader.rs index b3d8382acf5a4..72baa8d4ba755 100644 --- a/crates/bevy_scene/src/scene_loader.rs +++ b/crates/bevy_scene/src/scene_loader.rs @@ -4,6 +4,7 @@ use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_ecs::{FromResources, Resources}; use bevy_property::PropertyTypeRegistry; use bevy_type_registry::TypeRegistry; +use bevy_utils::BoxedFuture; use parking_lot::RwLock; use serde::de::DeserializeSeed; use std::sync::Arc; @@ -23,15 +24,21 @@ impl FromResources for SceneLoader { } impl AssetLoader for SceneLoader { - fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { - let registry = self.property_type_registry.read(); - let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; - let scene_deserializer = SceneDeserializer { - property_type_registry: ®istry, - }; - let scene = scene_deserializer.deserialize(&mut deserializer)?; - load_context.set_default_asset(LoadedAsset::new(scene)); - Ok(()) + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result<()>> { + Box::pin(async move { + let registry = self.property_type_registry.read(); + let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; + let scene_deserializer = SceneDeserializer { + property_type_registry: ®istry, + }; + let scene = scene_deserializer.deserialize(&mut deserializer)?; + load_context.set_default_asset(LoadedAsset::new(scene)); + Ok(()) + }) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_text/src/font_loader.rs b/crates/bevy_text/src/font_loader.rs index 41a1503e785cc..be4d3144583f5 100644 --- a/crates/bevy_text/src/font_loader.rs +++ b/crates/bevy_text/src/font_loader.rs @@ -1,15 +1,22 @@ use crate::Font; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; +use bevy_utils::BoxedFuture; #[derive(Default)] pub struct FontLoader; impl AssetLoader for FontLoader { - fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { - let font = Font::try_from_bytes(bytes.into())?; - load_context.set_default_asset(LoadedAsset::new(font)); - Ok(()) + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result<()>> { + Box::pin(async move { + let font = Font::try_from_bytes(bytes.into())?; + load_context.set_default_asset(LoadedAsset::new(font)); + Ok(()) + }) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index ac889854e647f..06639c4daf104 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -1,6 +1,9 @@ -pub use ahash::AHasher; use ahash::RandomState; +use std::{future::Future, pin::Pin}; + +pub use ahash::AHasher; +pub type BoxedFuture<'a, T> = Pin + Send + 'a>>; pub type HashMap = std::collections::HashMap; pub type HashSet = std::collections::HashSet; diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 774f624f46831..ec86a1a800dee 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -71,9 +71,12 @@ impl WinitWindows { let winit_window = winit_window_builder.build(&event_loop).unwrap(); - winit_window - .set_cursor_grab(window.cursor_locked()) - .unwrap(); + match winit_window.set_cursor_grab(window.cursor_locked()) { + Ok(_) => {} + Err(winit::error::ExternalError::NotSupported(_)) => {} + Err(err) => Err(err).unwrap(), + } + winit_window.set_cursor_visible(window.cursor_visible()); self.window_id_to_winit diff --git a/examples/asset/custom_asset.rs b/examples/asset/custom_asset.rs index 796d8b1d08b92..0009ab1b494fe 100644 --- a/examples/asset/custom_asset.rs +++ b/examples/asset/custom_asset.rs @@ -2,6 +2,7 @@ use bevy::{ asset::{AssetLoader, LoadContext, LoadedAsset}, prelude::*, type_registry::TypeUuid, + utils::BoxedFuture, }; use serde::Deserialize; @@ -15,10 +16,16 @@ pub struct CustomAsset { pub struct CustomAssetLoader; impl AssetLoader for CustomAssetLoader { - fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<(), anyhow::Error> { - let custom_asset = ron::de::from_bytes::(bytes)?; - load_context.set_default_asset(LoadedAsset::new(custom_asset)); - Ok(()) + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result<(), anyhow::Error>> { + Box::pin(async move { + let custom_asset = ron::de::from_bytes::(bytes)?; + load_context.set_default_asset(LoadedAsset::new(custom_asset)); + Ok(()) + }) } fn extensions(&self) -> &[&str] { diff --git a/examples/wasm/assets_wasm.rs b/examples/wasm/assets_wasm.rs index 957962bda2c21..15c5b6070235a 100644 --- a/examples/wasm/assets_wasm.rs +++ b/examples/wasm/assets_wasm.rs @@ -1,8 +1,12 @@ #[cfg(target_arch = "wasm32")] extern crate console_error_panic_hook; -use bevy::{asset::AssetLoader, prelude::*, type_registry::TypeUuid}; -use bevy_asset::{LoadContext, LoadedAsset}; +use bevy::{ + asset::{AssetLoader, AssetServerSettings, LoadContext, LoadedAsset}, + prelude::*, + type_registry::TypeUuid, + utils::BoxedFuture, +}; fn main() { #[cfg(target_arch = "wasm32")] @@ -12,17 +16,38 @@ fn main() { } App::build() + .add_resource(AssetServerSettings { + asset_folder: "/".to_string(), + }) .add_default_plugins() .add_asset::() .init_asset_loader::() - .add_startup_system(asset_system.system()) - .add_system(asset_events.system()) + .add_startup_system(load_asset.system()) + .add_system(print_asset.system()) .run(); } -fn asset_system(asset_server: Res) { - asset_server.load::("assets_wasm.rs"); - log::info!("hello wasm"); +struct State { + handle: Handle, + printed: bool, +} + +fn load_asset(mut commands: Commands, asset_server: Res) { + commands.insert_resource(State { + handle: asset_server.load("assets_wasm.rs"), + printed: false, + }); +} + +fn print_asset(mut state: ResMut, rust_sources: Res>) { + if state.printed { + return; + } + + if let Some(code) = rust_sources.get(&state.handle) { + log::info!("code: {}", code.0); + state.printed = true; + } } #[derive(Debug, TypeUuid)] @@ -33,11 +58,17 @@ pub struct RustSourceCode(pub String); pub struct RustSourceCodeLoader; impl AssetLoader for RustSourceCodeLoader { - fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<(), anyhow::Error> { - load_context.set_default_asset(LoadedAsset::new(RustSourceCode(String::from_utf8( - bytes.into(), - )?))); - Ok(()) + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result<(), anyhow::Error>> { + Box::pin(async move { + load_context.set_default_asset(LoadedAsset::new(RustSourceCode(String::from_utf8( + bytes.into(), + )?))); + Ok(()) + }) } fn extensions(&self) -> &[&str] { @@ -45,25 +76,3 @@ impl AssetLoader for RustSourceCodeLoader { EXT } } - -#[derive(Default)] -pub struct AssetEventsState { - reader: EventReader>, -} - -pub fn asset_events( - mut state: Local, - rust_sources: Res>, - events: Res>>, -) { - for event in state.reader.iter(&events) { - match event { - AssetEvent::Created { handle } => { - if let Some(code) = rust_sources.get(handle) { - log::info!("code: {}", code.0); - } - } - _ => continue, - }; - } -} diff --git a/src/lib.rs b/src/lib.rs index 35c9ae3b75dc6..11106c655f517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,7 @@ pub use bevy_scene as scene; pub use bevy_tasks as tasks; pub use bevy_transform as transform; pub use bevy_type_registry as type_registry; +pub use bevy_utils as utils; pub use bevy_window as window; #[cfg(feature = "bevy_audio")]