mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
parent
afc656701d
commit
a3012d94bb
9 changed files with 228 additions and 37 deletions
|
@ -94,6 +94,7 @@ log = "0.4"
|
|||
#wasm
|
||||
console_error_panic_hook = "0.1.6"
|
||||
console_log = { version = "0.2", features = ["color"] }
|
||||
anyhow = "1.0"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
|
@ -281,3 +282,8 @@ required-features = []
|
|||
name = "winit_wasm"
|
||||
path = "examples/wasm/winit_wasm.rs"
|
||||
required-features = ["bevy_winit"]
|
||||
|
||||
[[example]]
|
||||
name = "assets_wasm"
|
||||
path = "examples/wasm/assets_wasm.rs"
|
||||
required-features = ["bevy_winit"]
|
||||
|
|
|
@ -34,3 +34,10 @@ thiserror = "1.0"
|
|||
log = { version = "0.4", features = ["release_max_level_info"] }
|
||||
notify = { version = "5.0.0-pre.2", optional = true }
|
||||
parking_lot = "0.11.0"
|
||||
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"
|
||||
|
|
|
@ -9,11 +9,10 @@ use bevy_utils::{HashMap, HashSet};
|
|||
use crossbeam_channel::TryRecvError;
|
||||
use parking_lot::RwLock;
|
||||
use std::{
|
||||
env, fs, io,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// The type used for asset versioning
|
||||
|
@ -67,8 +66,7 @@ impl LoadState {
|
|||
/// Loads assets from the filesystem on background threads
|
||||
pub struct AssetServer {
|
||||
asset_folders: RwLock<Vec<PathBuf>>,
|
||||
asset_handlers: Arc<RwLock<Vec<Box<dyn AssetLoadRequestHandler>>>>,
|
||||
// TODO: this is a hack to enable retrieving generic AssetLoader<T>s. there must be a better way!
|
||||
asset_handlers: RwLock<Vec<Arc<dyn AssetLoadRequestHandler>>>,
|
||||
loaders: Vec<Resources>,
|
||||
task_pool: TaskPool,
|
||||
extension_to_handler_index: HashMap<String, usize>,
|
||||
|
@ -106,7 +104,7 @@ impl AssetServer {
|
|||
.insert(extension.to_string(), handler_index);
|
||||
}
|
||||
|
||||
asset_handlers.push(Box::new(asset_handler));
|
||||
asset_handlers.push(Arc::new(asset_handler));
|
||||
}
|
||||
|
||||
pub fn add_loader<TLoader, TAsset>(&mut self, loader: TLoader)
|
||||
|
@ -173,11 +171,12 @@ impl AssetServer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> {
|
||||
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
|
||||
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
|
||||
Ok(PathBuf::from(manifest_dir))
|
||||
} else {
|
||||
match env::current_exe() {
|
||||
match std::env::current_exe() {
|
||||
Ok(exe_path) => exe_path
|
||||
.parent()
|
||||
.ok_or(AssetServerError::InvalidRootPath)
|
||||
|
@ -187,6 +186,11 @@ impl AssetServer {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> {
|
||||
Ok(PathBuf::from("/"))
|
||||
}
|
||||
|
||||
// TODO: add type checking here. people shouldn't be able to request a Handle<Texture> for a Mesh asset
|
||||
pub fn load<T, P: AsRef<Path>>(&self, path: P) -> Result<Handle<T>, AssetServerError> {
|
||||
self.load_untyped(self.get_root_path()?.join(path))
|
||||
|
@ -272,19 +276,22 @@ impl AssetServer {
|
|||
version: new_version,
|
||||
};
|
||||
|
||||
let asset_handlers = self.asset_handlers.clone();
|
||||
let handlers = self.asset_handlers.read();
|
||||
let request_handler = handlers[load_request.handler_index].clone();
|
||||
|
||||
self.task_pool
|
||||
.spawn(async move {
|
||||
let handlers = asset_handlers.read();
|
||||
let request_handler = &handlers[load_request.handler_index];
|
||||
request_handler.handle_request(&load_request);
|
||||
request_handler.handle_request(&load_request).await;
|
||||
})
|
||||
.detach();
|
||||
|
||||
// TODO: watching each asset explicitly is a simpler implementation, its possible it would be more efficient to watch
|
||||
// folders instead (when possible)
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
Self::watch_path_for_changes(&mut self.filesystem_watcher.write(), path)?;
|
||||
Self::watch_path_for_changes(
|
||||
&mut self.filesystem_watcher.write(),
|
||||
path.to_owned(),
|
||||
)?;
|
||||
Ok(handle_id)
|
||||
} else {
|
||||
Err(AssetServerError::MissingAssetHandler)
|
||||
|
|
31
crates/bevy_asset/src/load_request/mod.rs
Normal file
31
crates/bevy_asset/src/load_request/mod.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use crate::{AssetLoader, AssetResult, AssetVersion, HandleId};
|
||||
use crossbeam_channel::Sender;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[path = "platform_default.rs"]
|
||||
mod platform_specific;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[path = "platform_wasm.rs"]
|
||||
mod platform_specific;
|
||||
|
||||
pub use platform_specific::*;
|
||||
|
||||
/// A request from an [AssetServer](crate::AssetServer) to load an asset.
|
||||
#[derive(Debug)]
|
||||
pub struct LoadRequest {
|
||||
pub path: PathBuf,
|
||||
pub handle_id: HandleId,
|
||||
pub handler_index: usize,
|
||||
pub version: AssetVersion,
|
||||
}
|
||||
|
||||
pub(crate) struct ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
TAsset: 'static,
|
||||
{
|
||||
sender: Sender<AssetResult<TAsset>>,
|
||||
loader: TLoader,
|
||||
}
|
|
@ -1,32 +1,16 @@
|
|||
use crate::{AssetLoadError, AssetLoader, AssetResult, AssetVersion, Handle, HandleId};
|
||||
use super::{ChannelAssetHandler, LoadRequest};
|
||||
use crate::{AssetLoadError, AssetLoader, AssetResult, Handle};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use crossbeam_channel::Sender;
|
||||
use fs::File;
|
||||
use io::Read;
|
||||
use std::{fs, io, path::PathBuf};
|
||||
|
||||
/// A request from an [AssetServer](crate::AssetServer) to load an asset.
|
||||
#[derive(Debug)]
|
||||
pub struct LoadRequest {
|
||||
pub path: PathBuf,
|
||||
pub handle_id: HandleId,
|
||||
pub handler_index: usize,
|
||||
pub version: AssetVersion,
|
||||
}
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
/// Handles load requests from an AssetServer
|
||||
pub trait AssetLoadRequestHandler: Send + Sync + 'static {
|
||||
fn handle_request(&self, load_request: &LoadRequest);
|
||||
fn extensions(&self) -> &[&str];
|
||||
}
|
||||
|
||||
pub(crate) struct ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
TAsset: 'static,
|
||||
{
|
||||
sender: Sender<AssetResult<TAsset>>,
|
||||
loader: TLoader,
|
||||
#[async_trait]
|
||||
pub trait AssetLoadRequestHandler: Send + Sync + 'static {
|
||||
async fn handle_request(&self, load_request: &LoadRequest);
|
||||
fn extensions(&self) -> &[&str];
|
||||
}
|
||||
|
||||
impl<TLoader, TAsset> ChannelAssetHandler<TLoader, TAsset>
|
||||
|
@ -53,12 +37,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<TLoader, TAsset> AssetLoadRequestHandler for ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + 'static,
|
||||
TAsset: Send + 'static,
|
||||
{
|
||||
fn handle_request(&self, load_request: &LoadRequest) {
|
||||
async fn handle_request(&self, load_request: &LoadRequest) {
|
||||
let result = self.load_asset(load_request);
|
||||
let asset_result = AssetResult {
|
||||
handle: Handle::from(load_request.handle_id),
|
62
crates/bevy_asset/src/load_request/platform_wasm.rs
Normal file
62
crates/bevy_asset/src/load_request/platform_wasm.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use super::{ChannelAssetHandler, LoadRequest};
|
||||
use crate::{AssetLoadError, AssetLoader, AssetResult, Handle};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use crossbeam_channel::Sender;
|
||||
|
||||
use js_sys::Uint8Array;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::Response;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait AssetLoadRequestHandler: Send + Sync + 'static {
|
||||
async fn handle_request(&self, load_request: &LoadRequest);
|
||||
fn extensions(&self) -> &[&str];
|
||||
}
|
||||
|
||||
impl<TLoader, TAsset> ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
{
|
||||
pub fn new(loader: TLoader, sender: Sender<AssetResult<TAsset>>) -> Self {
|
||||
ChannelAssetHandler { sender, loader }
|
||||
}
|
||||
|
||||
async fn load_asset(&self, load_request: &LoadRequest) -> Result<TAsset, AssetLoadError> {
|
||||
// TODO - get rid of some unwraps below (do some retrying maybe?)
|
||||
let window = web_sys::window().unwrap();
|
||||
let resp_value = JsFuture::from(window.fetch_with_str(load_request.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();
|
||||
let asset = self.loader.from_bytes(&load_request.path, bytes).unwrap();
|
||||
Ok(asset)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl<TLoader, TAsset> AssetLoadRequestHandler for ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + 'static,
|
||||
TAsset: Send + 'static,
|
||||
{
|
||||
async fn handle_request(&self, load_request: &LoadRequest) {
|
||||
let asset = self.load_asset(load_request).await;
|
||||
let asset_result = AssetResult {
|
||||
handle: Handle::from(load_request.handle_id),
|
||||
result: asset,
|
||||
path: load_request.path.clone(),
|
||||
version: load_request.version,
|
||||
};
|
||||
self.sender
|
||||
.send(asset_result)
|
||||
.expect("loaded asset should have been sent");
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
self.loader.extensions()
|
||||
}
|
||||
}
|
|
@ -16,3 +16,5 @@ event-listener = "2.4.0"
|
|||
async-executor = "1.3.0"
|
||||
async-channel = "1.4.2"
|
||||
num_cpus = "1"
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen-futures = "0.4"
|
||||
|
|
|
@ -82,6 +82,32 @@ impl TaskPool {
|
|||
.map(|result| result.lock().unwrap().take().unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Spawns a static future onto the JS event loop. For now it is returning FakeTask
|
||||
// instance with no-op detach method. Returning real Task is possible here, but tricky:
|
||||
// future is running on JS event loop, Task is running on async_executor::LocalExecutor
|
||||
// so some proxy future is needed. Moreover currently we don't have long-living
|
||||
// LocalExecutor here (above `spawn` implementation creates temporary one)
|
||||
// But for typical use cases it seems that current implementation should be sufficient:
|
||||
// caller can spawn long-running future writing results to some channel / event queue
|
||||
// and simply call detach on returned Task (like AssetServer does) - spawned future
|
||||
// can write results to some channel / event queue.
|
||||
|
||||
pub fn spawn<T>(&self, future: impl Future<Output = T> + 'static) -> FakeTask
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
future.await;
|
||||
});
|
||||
FakeTask
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FakeTask;
|
||||
|
||||
impl FakeTask {
|
||||
pub fn detach(self) {}
|
||||
}
|
||||
|
||||
pub struct Scope<'scope, T> {
|
||||
|
|
65
examples/wasm/assets_wasm.rs
Normal file
65
examples/wasm/assets_wasm.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
extern crate console_error_panic_hook;
|
||||
use bevy::{asset::AssetLoader, prelude::*};
|
||||
use std::{panic, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
console_log::init_with_level(log::Level::Debug).expect("cannot initialize console_log");
|
||||
|
||||
App::build()
|
||||
.add_default_plugins()
|
||||
.add_asset::<RustSourceCode>()
|
||||
.add_asset_loader::<RustSourceCode, RustSourceCodeLoader>()
|
||||
.add_startup_system(asset_system.system())
|
||||
.add_system(asset_events.system())
|
||||
.run();
|
||||
}
|
||||
|
||||
fn asset_system(asset_server: Res<AssetServer>) {
|
||||
asset_server
|
||||
.load::<Handle<RustSourceCode>, _>(PathBuf::from("assets_wasm.rs"))
|
||||
.unwrap();
|
||||
log::info!("hello wasm");
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RustSourceCode(pub String);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RustSourceCodeLoader;
|
||||
impl AssetLoader<RustSourceCode> for RustSourceCodeLoader {
|
||||
fn from_bytes(
|
||||
&self,
|
||||
_asset_path: &std::path::Path,
|
||||
bytes: Vec<u8>,
|
||||
) -> Result<RustSourceCode, anyhow::Error> {
|
||||
Ok(RustSourceCode(String::from_utf8(bytes)?))
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
static EXT: &[&str] = &["rs"];
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue