asset: WasmAssetIo (#703)

asset: WasmAssetIo
This commit is contained in:
Carter Anderson 2020-10-19 17:29:31 -07:00 committed by GitHub
parent 03bc5d7fdd
commit f88cfabdde
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 410 additions and 264 deletions

View file

@ -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"

View file

@ -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<RwLock<HashMap<HandleId, usize>>>,
}
pub struct AssetServerInternal<TAssetIo: AssetIo = FileAssetIo> {
pub(crate) asset_io: TAssetIo,
pub struct AssetServerInternal {
pub(crate) asset_io: Box<dyn AssetIo>,
pub(crate) asset_ref_counter: AssetRefCounter,
pub(crate) asset_sources: Arc<RwLock<HashMap<SourcePathId, SourceInfo>>>,
pub(crate) asset_lifecycles: Arc<RwLock<HashMap<Uuid, Box<dyn AssetLifecycle>>>>,
@ -47,11 +47,11 @@ pub struct AssetServerInternal<TAssetIo: AssetIo = FileAssetIo> {
}
/// Loads assets from the filesystem on background threads
pub struct AssetServer<TAssetIo: AssetIo = FileAssetIo> {
pub(crate) server: Arc<AssetServerInternal<TAssetIo>>,
pub struct AssetServer {
pub(crate) server: Arc<AssetServerInternal>,
}
impl<TAssetIo: AssetIo> Clone for AssetServer<TAssetIo> {
impl Clone for AssetServer {
fn clone(&self) -> Self {
Self {
server: self.server.clone(),
@ -59,8 +59,8 @@ impl<TAssetIo: AssetIo> Clone for AssetServer<TAssetIo> {
}
}
impl<TAssetIo: AssetIo> AssetServer<TAssetIo> {
pub fn new(source_io: TAssetIo, task_pool: TaskPool) -> Self {
impl AssetServer {
pub fn new<T: AssetIo>(source_io: T, task_pool: TaskPool) -> Self {
AssetServer {
server: Arc::new(AssetServerInternal {
loaders: Default::default(),
@ -70,7 +70,7 @@ impl<TAssetIo: AssetIo> AssetServer<TAssetIo> {
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<TAssetIo: AssetIo> AssetServer<TAssetIo> {
}
// TODO: properly set failed LoadState in all failure cases
fn load_sync<'a, P: Into<AssetPath<'a>>>(
async fn load_async<'a, P: Into<AssetPath<'a>>>(
&self,
path: P,
force: bool,
@ -221,17 +221,18 @@ impl<TAssetIo: AssetIo> AssetServer<TAssetIo> {
};
// 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<TAssetIo: AssetIo> AssetServer<TAssetIo> {
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()

View file

@ -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<Vec<u8>, AssetIoError>;
fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError>;
fn read_directory(
&self,
path: &Path,
) -> Result<Box<dyn Iterator<Item = PathBuf>>, 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<Vec<u8>, AssetIoError> {
async fn load_path(&self, path: &Path) -> Result<Vec<u8>, 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<AssetServer>) {
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::<FileAssetIo>() {
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<AssetServer>) {
{
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);
}
}

View file

@ -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<Vec<u8>, AssetIoError>;
fn read_directory(
&self,
path: &Path,
) -> Result<Box<dyn Iterator<Item = PathBuf>>, 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);

View file

@ -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<P: AsRef<Path>>(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<Vec<u8>, 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<Box<dyn Iterator<Item = PathBuf>>, AssetIoError> {
Ok(Box::new(std::iter::empty::<PathBuf>()))
}
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()
}
}

View file

@ -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());
}
}

View file

@ -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<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
self.asset_io.load_path(path.as_ref())
pub async fn read_asset_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
self.asset_io.load_path(path.as_ref()).await
}
pub fn get_asset_metas(&self) -> Vec<AssetMeta> {

View file

@ -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"

View file

@ -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<Result<()>> {
load_context.set_default_asset(LoadedAsset::new(AudioSource {
bytes: bytes.into(),
}));
Ok(())
Box::pin(async move { Ok(()) })
}
fn extensions(&self) -> &[&str] {

View file

@ -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<PrimitiveTopology, GltfError> {
}
}
fn load_buffers(
async fn load_buffers(
gltf: &gltf::Gltf,
load_context: &LoadContext,
load_context: &LoadContext<'_>,
asset_path: &Path,
) -> Result<Vec<Vec<u8>>, 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);
}
}

View file

@ -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"
);
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());
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;
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());
}
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,
);
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(())
load_context.set_default_asset(LoadedAsset::new(texture));
Ok(())
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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;
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.
// 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();
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()
)
};
// 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.
// 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 dyn_img = image::load_from_memory_with_format(bytes, img_format)?;
let width;
let height;
let width;
let height;
let data: Vec<u8>;
let format: TextureFormat;
let data: Vec<u8>;
let format: TextureFormat;
match dyn_img {
image::DynamicImage::ImageLuma8(i) => {
width = i.width();
height = i.height();
format = TextureFormat::R8Unorm;
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::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;
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::ImageRgba8(i) => {
width = i.width();
height = i.height();
format = TextureFormat::Rgba8UnormSrgb;
data = i.into_raw();
}
image::DynamicImage::ImageRgba8(i) => {
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::ImageBgr8(i) => {
let i = image::DynamicImage::ImageBgr8(i).into_bgra();
width = i.width();
height = i.height();
format = TextureFormat::Bgra8UnormSrgb;
width = i.width();
height = i.height();
format = TextureFormat::Bgra8UnormSrgb;
data = i.into_raw();
}
image::DynamicImage::ImageBgra8(i) => {
width = i.width();
height = i.height();
format = TextureFormat::Bgra8UnormSrgb;
data = i.into_raw();
}
image::DynamicImage::ImageBgra8(i) => {
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::ImageLuma16(i) => {
width = i.width();
height = i.height();
format = TextureFormat::R16Uint;
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();
}
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::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::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 = 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();
}
}
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] {

View file

@ -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: &registry,
};
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: &registry,
};
let scene = scene_deserializer.deserialize(&mut deserializer)?;
load_context.set_default_asset(LoadedAsset::new(scene));
Ok(())
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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] {

View file

@ -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<Box<dyn Future<Output = T> + Send + 'a>>;
pub type HashMap<K, V> = std::collections::HashMap<K, V, RandomState>;
pub type HashSet<K> = std::collections::HashSet<K, RandomState>;

View file

@ -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

View file

@ -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::<CustomAsset>(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::<CustomAsset>(bytes)?;
load_context.set_default_asset(LoadedAsset::new(custom_asset));
Ok(())
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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::<RustSourceCode>()
.init_asset_loader::<RustSourceCodeLoader>()
.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<AssetServer>) {
asset_server.load::<RustSourceCode, _>("assets_wasm.rs");
log::info!("hello wasm");
struct State {
handle: Handle<RustSourceCode>,
printed: bool,
}
fn load_asset(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.insert_resource(State {
handle: asset_server.load("assets_wasm.rs"),
printed: false,
});
}
fn print_asset(mut state: ResMut<State>, rust_sources: Res<Assets<RustSourceCode>>) {
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<AssetEvent<RustSourceCode>>,
}
pub fn asset_events(
mut state: Local<AssetEventsState>,
rust_sources: Res<Assets<RustSourceCode>>,
events: Res<Events<AssetEvent<RustSourceCode>>>,
) {
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,
};
}
}

View file

@ -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")]