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
|
#wasm
|
||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
console_log = { version = "0.2", features = ["color"] }
|
console_log = { version = "0.2", features = ["color"] }
|
||||||
|
anyhow = "1.0"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "hello_world"
|
name = "hello_world"
|
||||||
|
@ -281,3 +282,8 @@ required-features = []
|
||||||
name = "winit_wasm"
|
name = "winit_wasm"
|
||||||
path = "examples/wasm/winit_wasm.rs"
|
path = "examples/wasm/winit_wasm.rs"
|
||||||
required-features = ["bevy_winit"]
|
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"] }
|
log = { version = "0.4", features = ["release_max_level_info"] }
|
||||||
notify = { version = "5.0.0-pre.2", optional = true }
|
notify = { version = "5.0.0-pre.2", optional = true }
|
||||||
parking_lot = "0.11.0"
|
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 crossbeam_channel::TryRecvError;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::{
|
use std::{
|
||||||
env, fs, io,
|
fs, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// The type used for asset versioning
|
/// The type used for asset versioning
|
||||||
|
@ -67,8 +66,7 @@ impl LoadState {
|
||||||
/// Loads assets from the filesystem on background threads
|
/// Loads assets from the filesystem on background threads
|
||||||
pub struct AssetServer {
|
pub struct AssetServer {
|
||||||
asset_folders: RwLock<Vec<PathBuf>>,
|
asset_folders: RwLock<Vec<PathBuf>>,
|
||||||
asset_handlers: Arc<RwLock<Vec<Box<dyn AssetLoadRequestHandler>>>>,
|
asset_handlers: RwLock<Vec<Arc<dyn AssetLoadRequestHandler>>>,
|
||||||
// TODO: this is a hack to enable retrieving generic AssetLoader<T>s. there must be a better way!
|
|
||||||
loaders: Vec<Resources>,
|
loaders: Vec<Resources>,
|
||||||
task_pool: TaskPool,
|
task_pool: TaskPool,
|
||||||
extension_to_handler_index: HashMap<String, usize>,
|
extension_to_handler_index: HashMap<String, usize>,
|
||||||
|
@ -106,7 +104,7 @@ impl AssetServer {
|
||||||
.insert(extension.to_string(), handler_index);
|
.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)
|
pub fn add_loader<TLoader, TAsset>(&mut self, loader: TLoader)
|
||||||
|
@ -173,11 +171,12 @@ impl AssetServer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> {
|
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))
|
Ok(PathBuf::from(manifest_dir))
|
||||||
} else {
|
} else {
|
||||||
match env::current_exe() {
|
match std::env::current_exe() {
|
||||||
Ok(exe_path) => exe_path
|
Ok(exe_path) => exe_path
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or(AssetServerError::InvalidRootPath)
|
.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
|
// 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> {
|
pub fn load<T, P: AsRef<Path>>(&self, path: P) -> Result<Handle<T>, AssetServerError> {
|
||||||
self.load_untyped(self.get_root_path()?.join(path))
|
self.load_untyped(self.get_root_path()?.join(path))
|
||||||
|
@ -272,19 +276,22 @@ impl AssetServer {
|
||||||
version: new_version,
|
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
|
self.task_pool
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let handlers = asset_handlers.read();
|
request_handler.handle_request(&load_request).await;
|
||||||
let request_handler = &handlers[load_request.handler_index];
|
|
||||||
request_handler.handle_request(&load_request);
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
// TODO: watching each asset explicitly is a simpler implementation, its possible it would be more efficient to watch
|
// TODO: watching each asset explicitly is a simpler implementation, its possible it would be more efficient to watch
|
||||||
// folders instead (when possible)
|
// folders instead (when possible)
|
||||||
#[cfg(feature = "filesystem_watcher")]
|
#[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)
|
Ok(handle_id)
|
||||||
} else {
|
} else {
|
||||||
Err(AssetServerError::MissingAssetHandler)
|
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 anyhow::Result;
|
||||||
|
use async_trait::async_trait;
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use fs::File;
|
use std::{fs::File, io::Read};
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles load requests from an AssetServer
|
/// 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>
|
#[async_trait]
|
||||||
where
|
pub trait AssetLoadRequestHandler: Send + Sync + 'static {
|
||||||
TLoader: AssetLoader<TAsset>,
|
async fn handle_request(&self, load_request: &LoadRequest);
|
||||||
TAsset: 'static,
|
fn extensions(&self) -> &[&str];
|
||||||
{
|
|
||||||
sender: Sender<AssetResult<TAsset>>,
|
|
||||||
loader: TLoader,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TLoader, TAsset> ChannelAssetHandler<TLoader, TAsset>
|
impl<TLoader, TAsset> ChannelAssetHandler<TLoader, TAsset>
|
||||||
|
@ -53,12 +37,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl<TLoader, TAsset> AssetLoadRequestHandler for ChannelAssetHandler<TLoader, TAsset>
|
impl<TLoader, TAsset> AssetLoadRequestHandler for ChannelAssetHandler<TLoader, TAsset>
|
||||||
where
|
where
|
||||||
TLoader: AssetLoader<TAsset> + 'static,
|
TLoader: AssetLoader<TAsset> + 'static,
|
||||||
TAsset: Send + '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 result = self.load_asset(load_request);
|
||||||
let asset_result = AssetResult {
|
let asset_result = AssetResult {
|
||||||
handle: Handle::from(load_request.handle_id),
|
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-executor = "1.3.0"
|
||||||
async-channel = "1.4.2"
|
async-channel = "1.4.2"
|
||||||
num_cpus = "1"
|
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())
|
.map(|result| result.lock().unwrap().take().unwrap())
|
||||||
.collect()
|
.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> {
|
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