Skip to content

Commit f88cfab

Browse files
authored
asset: WasmAssetIo (#703)
asset: WasmAssetIo
1 parent 03bc5d7 commit f88cfab

File tree

19 files changed

+422
-276
lines changed

19 files changed

+422
-276
lines changed

crates/bevy_asset/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,10 @@ log = { version = "0.4", features = ["release_max_level_info"] }
3737
notify = { version = "5.0.0-pre.2", optional = true }
3838
parking_lot = "0.11.0"
3939
rand = "0.7.3"
40+
async-trait = "0.1.40"
41+
42+
[target.'cfg(target_arch = "wasm32")'.dependencies]
43+
wasm-bindgen = { version = "0.2" }
44+
web-sys = { version = "0.3", features = ["Request", "Window", "Response"]}
45+
wasm-bindgen-futures = "0.4"
46+
js-sys = "0.3"

crates/bevy_asset/src/asset_server.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::{
22
path::{AssetPath, AssetPathId, SourcePathId},
33
Asset, AssetIo, AssetIoError, AssetLifecycle, AssetLifecycleChannel, AssetLifecycleEvent,
4-
AssetLoader, Assets, FileAssetIo, Handle, HandleId, HandleUntyped, LabelId, LoadContext,
5-
LoadState, RefChange, RefChangeChannel, SourceInfo, SourceMeta,
4+
AssetLoader, Assets, Handle, HandleId, HandleUntyped, LabelId, LoadContext, LoadState,
5+
RefChange, RefChangeChannel, SourceInfo, SourceMeta,
66
};
77
use anyhow::Result;
88
use bevy_ecs::Res;
@@ -35,8 +35,8 @@ pub(crate) struct AssetRefCounter {
3535
pub(crate) ref_counts: Arc<RwLock<HashMap<HandleId, usize>>>,
3636
}
3737

38-
pub struct AssetServerInternal<TAssetIo: AssetIo = FileAssetIo> {
39-
pub(crate) asset_io: TAssetIo,
38+
pub struct AssetServerInternal {
39+
pub(crate) asset_io: Box<dyn AssetIo>,
4040
pub(crate) asset_ref_counter: AssetRefCounter,
4141
pub(crate) asset_sources: Arc<RwLock<HashMap<SourcePathId, SourceInfo>>>,
4242
pub(crate) asset_lifecycles: Arc<RwLock<HashMap<Uuid, Box<dyn AssetLifecycle>>>>,
@@ -47,20 +47,20 @@ pub struct AssetServerInternal<TAssetIo: AssetIo = FileAssetIo> {
4747
}
4848

4949
/// Loads assets from the filesystem on background threads
50-
pub struct AssetServer<TAssetIo: AssetIo = FileAssetIo> {
51-
pub(crate) server: Arc<AssetServerInternal<TAssetIo>>,
50+
pub struct AssetServer {
51+
pub(crate) server: Arc<AssetServerInternal>,
5252
}
5353

54-
impl<TAssetIo: AssetIo> Clone for AssetServer<TAssetIo> {
54+
impl Clone for AssetServer {
5555
fn clone(&self) -> Self {
5656
Self {
5757
server: self.server.clone(),
5858
}
5959
}
6060
}
6161

62-
impl<TAssetIo: AssetIo> AssetServer<TAssetIo> {
63-
pub fn new(source_io: TAssetIo, task_pool: TaskPool) -> Self {
62+
impl AssetServer {
63+
pub fn new<T: AssetIo>(source_io: T, task_pool: TaskPool) -> Self {
6464
AssetServer {
6565
server: Arc::new(AssetServerInternal {
6666
loaders: Default::default(),
@@ -70,7 +70,7 @@ impl<TAssetIo: AssetIo> AssetServer<TAssetIo> {
7070
handle_to_path: Default::default(),
7171
asset_lifecycles: Default::default(),
7272
task_pool,
73-
asset_io: source_io,
73+
asset_io: Box::new(source_io),
7474
}),
7575
}
7676
}
@@ -180,7 +180,7 @@ impl<TAssetIo: AssetIo> AssetServer<TAssetIo> {
180180
}
181181

182182
// TODO: properly set failed LoadState in all failure cases
183-
fn load_sync<'a, P: Into<AssetPath<'a>>>(
183+
async fn load_async<'a, P: Into<AssetPath<'a>>>(
184184
&self,
185185
path: P,
186186
force: bool,
@@ -221,17 +221,18 @@ impl<TAssetIo: AssetIo> AssetServer<TAssetIo> {
221221
};
222222

223223
// load the asset bytes
224-
let bytes = self.server.asset_io.load_path(asset_path.path())?;
224+
let bytes = self.server.asset_io.load_path(asset_path.path()).await?;
225225

226226
// load the asset source using the corresponding AssetLoader
227227
let mut load_context = LoadContext::new(
228228
asset_path.path(),
229229
&self.server.asset_ref_counter.channel,
230-
&self.server.asset_io,
230+
&*self.server.asset_io,
231231
version,
232232
);
233233
asset_loader
234234
.load(&bytes, &mut load_context)
235+
.await
235236
.map_err(AssetServerError::AssetLoaderError)?;
236237

237238
// 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> {
291292
self.server
292293
.task_pool
293294
.spawn(async move {
294-
server.load_sync(owned_path, force).unwrap();
295+
server.load_async(owned_path, force).await.unwrap();
295296
})
296297
.detach();
297298
asset_path.into()

crates/bevy_asset/src/io.rs renamed to crates/bevy_asset/src/io/file_asset_io.rs

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use crate::{filesystem_watcher::FilesystemWatcher, AssetIo, AssetIoError, AssetServer};
12
use anyhow::Result;
3+
use async_trait::async_trait;
24
use bevy_ecs::Res;
35
use bevy_utils::HashSet;
46
use crossbeam_channel::TryRecvError;
@@ -10,33 +12,6 @@ use std::{
1012
path::{Path, PathBuf},
1113
sync::Arc,
1214
};
13-
use thiserror::Error;
14-
15-
use crate::{filesystem_watcher::FilesystemWatcher, AssetServer};
16-
17-
/// Errors that occur while loading assets
18-
#[derive(Error, Debug)]
19-
pub enum AssetIoError {
20-
#[error("Path not found")]
21-
NotFound(PathBuf),
22-
#[error("Encountered an io error while loading asset.")]
23-
Io(#[from] io::Error),
24-
#[error("Failed to watch path")]
25-
PathWatchError(PathBuf),
26-
}
27-
28-
/// Handles load requests from an AssetServer
29-
pub trait AssetIo: Send + Sync + 'static {
30-
fn load_path(&self, path: &Path) -> Result<Vec<u8>, AssetIoError>;
31-
fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError>;
32-
fn read_directory(
33-
&self,
34-
path: &Path,
35-
) -> Result<Box<dyn Iterator<Item = PathBuf>>, AssetIoError>;
36-
fn is_directory(&self, path: &Path) -> bool;
37-
fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError>;
38-
fn watch_for_changes(&self) -> Result<(), AssetIoError>;
39-
}
4015

4116
pub struct FileAssetIo {
4217
root_path: PathBuf,
@@ -67,8 +42,9 @@ impl FileAssetIo {
6742
}
6843
}
6944

45+
#[async_trait]
7046
impl AssetIo for FileAssetIo {
71-
fn load_path(&self, path: &Path) -> Result<Vec<u8>, AssetIoError> {
47+
async fn load_path(&self, path: &Path) -> Result<Vec<u8>, AssetIoError> {
7248
let mut bytes = Vec::new();
7349
match File::open(self.root_path.join(path)) {
7450
Ok(mut file) => {
@@ -98,15 +74,6 @@ impl AssetIo for FileAssetIo {
9874
)))
9975
}
10076

101-
fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError> {
102-
let path = self.root_path.join(path);
103-
if let Some(parent_path) = path.parent() {
104-
fs::create_dir_all(parent_path)?;
105-
}
106-
107-
Ok(fs::write(self.root_path.join(path), bytes)?)
108-
}
109-
11077
fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError> {
11178
#[cfg(feature = "filesystem_watcher")]
11279
{
@@ -139,7 +106,13 @@ impl AssetIo for FileAssetIo {
139106
#[cfg(feature = "filesystem_watcher")]
140107
pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
141108
let mut changed = HashSet::default();
142-
let watcher = asset_server.server.asset_io.filesystem_watcher.read();
109+
let asset_io =
110+
if let Some(asset_io) = asset_server.server.asset_io.downcast_ref::<FileAssetIo>() {
111+
asset_io
112+
} else {
113+
return;
114+
};
115+
let watcher = asset_io.filesystem_watcher.read();
143116
if let Some(ref watcher) = *watcher {
144117
loop {
145118
let event = match watcher.receiver.try_recv() {
@@ -155,9 +128,7 @@ pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
155128
{
156129
for path in paths.iter() {
157130
if !changed.contains(path) {
158-
let relative_path = path
159-
.strip_prefix(&asset_server.server.asset_io.root_path)
160-
.unwrap();
131+
let relative_path = path.strip_prefix(&asset_io.root_path).unwrap();
161132
let _ = asset_server.load_untracked(relative_path, true);
162133
}
163134
}

crates/bevy_asset/src/io/mod.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#[cfg(not(target_arch = "wasm32"))]
2+
mod file_asset_io;
3+
#[cfg(target_arch = "wasm32")]
4+
mod wasm_asset_io;
5+
6+
#[cfg(not(target_arch = "wasm32"))]
7+
pub use file_asset_io::*;
8+
#[cfg(target_arch = "wasm32")]
9+
pub use wasm_asset_io::*;
10+
11+
use anyhow::Result;
12+
use async_trait::async_trait;
13+
use downcast_rs::{impl_downcast, Downcast};
14+
use std::{
15+
io,
16+
path::{Path, PathBuf},
17+
};
18+
use thiserror::Error;
19+
20+
/// Errors that occur while loading assets
21+
#[derive(Error, Debug)]
22+
pub enum AssetIoError {
23+
#[error("Path not found")]
24+
NotFound(PathBuf),
25+
#[error("Encountered an io error while loading asset.")]
26+
Io(#[from] io::Error),
27+
#[error("Failed to watch path")]
28+
PathWatchError(PathBuf),
29+
}
30+
31+
/// Handles load requests from an AssetServer
32+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
33+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
34+
pub trait AssetIo: Downcast + Send + Sync + 'static {
35+
async fn load_path(&self, path: &Path) -> Result<Vec<u8>, AssetIoError>;
36+
fn read_directory(
37+
&self,
38+
path: &Path,
39+
) -> Result<Box<dyn Iterator<Item = PathBuf>>, AssetIoError>;
40+
fn is_directory(&self, path: &Path) -> bool;
41+
fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError>;
42+
fn watch_for_changes(&self) -> Result<(), AssetIoError>;
43+
}
44+
45+
impl_downcast!(AssetIo);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use crate::{AssetIo, AssetIoError};
2+
use anyhow::Result;
3+
use async_trait::async_trait;
4+
use js_sys::Uint8Array;
5+
use std::path::{Path, PathBuf};
6+
use wasm_bindgen::JsCast;
7+
use wasm_bindgen_futures::JsFuture;
8+
use web_sys::Response;
9+
10+
pub struct WasmAssetIo {
11+
root_path: PathBuf,
12+
}
13+
14+
impl WasmAssetIo {
15+
pub fn new<P: AsRef<Path>>(path: P) -> Self {
16+
WasmAssetIo {
17+
root_path: path.as_ref().to_owned(),
18+
}
19+
}
20+
}
21+
22+
#[async_trait(?Send)]
23+
impl AssetIo for WasmAssetIo {
24+
async fn load_path(&self, path: &Path) -> Result<Vec<u8>, AssetIoError> {
25+
let path = self.root_path.join(path);
26+
let window = web_sys::window().unwrap();
27+
let resp_value = JsFuture::from(window.fetch_with_str(path.to_str().unwrap()))
28+
.await
29+
.unwrap();
30+
let resp: Response = resp_value.dyn_into().unwrap();
31+
let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap();
32+
let bytes = Uint8Array::new(&data).to_vec();
33+
Ok(bytes)
34+
}
35+
36+
fn read_directory(
37+
&self,
38+
_path: &Path,
39+
) -> Result<Box<dyn Iterator<Item = PathBuf>>, AssetIoError> {
40+
Ok(Box::new(std::iter::empty::<PathBuf>()))
41+
}
42+
43+
fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> {
44+
Ok(())
45+
}
46+
47+
fn watch_for_changes(&self) -> Result<(), AssetIoError> {
48+
Ok(())
49+
}
50+
51+
fn is_directory(&self, path: &Path) -> bool {
52+
self.root_path.join(path).is_dir()
53+
}
54+
}

crates/bevy_asset/src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod asset_server;
22
mod assets;
3-
#[cfg(feature = "filesystem_watcher")]
3+
#[cfg(all(feature = "filesystem_watcher", not(target_arch = "wasm32")))]
44
mod filesystem_watcher;
55
mod handle;
66
mod info;
@@ -37,7 +37,7 @@ use bevy_type_registry::RegisterType;
3737
pub struct AssetPlugin;
3838

3939
pub struct AssetServerSettings {
40-
asset_folder: String,
40+
pub asset_folder: String,
4141
}
4242

4343
impl Default for AssetServerSettings {
@@ -61,7 +61,11 @@ impl Plugin for AssetPlugin {
6161
let settings = app
6262
.resources_mut()
6363
.get_or_insert_with(AssetServerSettings::default);
64+
65+
#[cfg(not(target_arch = "wasm32"))]
6466
let source = FileAssetIo::new(&settings.asset_folder);
67+
#[cfg(target_arch = "wasm32")]
68+
let source = WasmAssetIo::new(&settings.asset_folder);
6569
AssetServer::new(source, task_pool)
6670
};
6771

@@ -74,7 +78,7 @@ impl Plugin for AssetPlugin {
7478
asset_server::free_unused_assets_system.system(),
7579
);
7680

77-
#[cfg(feature = "filesystem_watcher")]
81+
#[cfg(all(feature = "filesystem_watcher", not(target_arch = "wasm32")))]
7882
app.add_system_to_stage(stage::LOAD_ASSETS, io::filesystem_watcher_system.system());
7983
}
8084
}

crates/bevy_asset/src/loader.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ use crate::{
55
use anyhow::Result;
66
use bevy_ecs::{Res, ResMut, Resource};
77
use bevy_type_registry::{TypeUuid, TypeUuidDynamic};
8-
use bevy_utils::HashMap;
8+
use bevy_utils::{BoxedFuture, HashMap};
99
use crossbeam_channel::{Receiver, Sender};
1010
use downcast_rs::{impl_downcast, Downcast};
1111
use std::path::Path;
1212

1313
/// A loader for an asset source
1414
pub trait AssetLoader: Send + Sync + 'static {
15-
fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<(), anyhow::Error>;
15+
fn load<'a>(
16+
&'a self,
17+
bytes: &'a [u8],
18+
load_context: &'a mut LoadContext,
19+
) -> BoxedFuture<'a, Result<(), anyhow::Error>>;
1620
fn extensions(&self) -> &[&str];
1721
}
1822

@@ -94,8 +98,8 @@ impl<'a> LoadContext<'a> {
9498
Handle::strong(id.into(), self.ref_change_channel.sender.clone())
9599
}
96100

97-
pub fn read_asset_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
98-
self.asset_io.load_path(path.as_ref())
101+
pub async fn read_asset_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
102+
self.asset_io.load_path(path.as_ref()).await
99103
}
100104

101105
pub fn get_asset_metas(&self) -> Vec<AssetMeta> {

crates/bevy_audio/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ bevy_app = { path = "../bevy_app", version = "0.2.1" }
1818
bevy_asset = { path = "../bevy_asset", version = "0.2.1" }
1919
bevy_ecs = { path = "../bevy_ecs", version = "0.2.1" }
2020
bevy_type_registry = { path = "../bevy_type_registry", version = "0.2.1" }
21+
bevy_utils = { path = "../bevy_utils", version = "0.2.1" }
2122

2223
# other
2324
anyhow = "1.0"

0 commit comments

Comments
 (0)