mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
AssetServer: multithreaded sync/async asset loading
This commit is contained in:
parent
8a61ef48d3
commit
4e1abea161
18 changed files with 903 additions and 234 deletions
94
.vscode/launch.json
vendored
94
.vscode/launch.json
vendored
|
@ -857,6 +857,25 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in library 'bevy_text'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--lib",
|
||||
"--package=bevy_text"
|
||||
],
|
||||
"filter": {
|
||||
"name": "bevy_text",
|
||||
"kind": "lib"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
@ -1377,6 +1396,44 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug example 'load_asset_folder'",
|
||||
"env": { "CARGO_MANIFEST_DIR": "${workspaceFolder}" },
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--example=load_asset_folder",
|
||||
"--package=bevy"
|
||||
],
|
||||
"filter": {
|
||||
"name": "load_asset_folder",
|
||||
"kind": "example"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in example 'load_asset_folder'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--example=load_asset_folder",
|
||||
"--package=bevy"
|
||||
],
|
||||
"filter": {
|
||||
"name": "load_asset_folder",
|
||||
"kind": "example"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
@ -1747,6 +1804,43 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug example 'text'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--example=text",
|
||||
"--package=bevy"
|
||||
],
|
||||
"filter": {
|
||||
"name": "text",
|
||||
"kind": "example"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in example 'text'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--example=text",
|
||||
"--package=bevy"
|
||||
],
|
||||
"filter": {
|
||||
"name": "text",
|
||||
"kind": "example"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
|
|
@ -9,4 +9,7 @@ bevy_app = { path = "../bevy_app" }
|
|||
bevy_core = { path = "../bevy_core" }
|
||||
legion = { path = "../bevy_legion" }
|
||||
|
||||
uuid = { version = "0.8", features = ["v4", "serde"] }
|
||||
uuid = { version = "0.8", features = ["v4", "serde"] }
|
||||
crossbeam-channel = "0.4.2"
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
55
crates/bevy_asset/src/asset_path.rs
Normal file
55
crates/bevy_asset/src/asset_path.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AssetPath {
|
||||
pub path: Cow<'static, str>,
|
||||
pub extension: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl From<&Path> for AssetPath {
|
||||
fn from(path: &Path) -> Self {
|
||||
AssetPath {
|
||||
path: Cow::Owned(
|
||||
path.to_str()
|
||||
.expect("Path should be a valid string.")
|
||||
.to_string(),
|
||||
),
|
||||
extension: path.extension().map(|e| {
|
||||
Cow::Owned(
|
||||
e.to_str()
|
||||
.expect("Extension should be a valid string.")
|
||||
.to_string(),
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PathBuf> for AssetPath {
|
||||
fn from(path: &PathBuf) -> Self {
|
||||
AssetPath {
|
||||
path: Cow::Owned(
|
||||
path.to_str()
|
||||
.expect("Path should be a valid string.")
|
||||
.to_string(),
|
||||
),
|
||||
extension: path.extension().map(|e| {
|
||||
Cow::Owned(
|
||||
e.to_str()
|
||||
.expect("Extension should be a valid string.")
|
||||
.to_string(),
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for AssetPath {
|
||||
fn from(path: &str) -> Self {
|
||||
let path = Path::new(path);
|
||||
AssetPath::from(path)
|
||||
}
|
||||
}
|
245
crates/bevy_asset/src/asset_server.rs
Normal file
245
crates/bevy_asset/src/asset_server.rs
Normal file
|
@ -0,0 +1,245 @@
|
|||
use crate::{Assets, Handle, HandleId, LoadRequest, AssetLoadError, AssetLoadRequestHandler, AssetLoader, AssetPath};
|
||||
use anyhow::Result;
|
||||
use legion::prelude::Resources;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env, fs, io,
|
||||
path::Path,
|
||||
sync::{Arc, RwLock},
|
||||
thread,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AssetServerError {
|
||||
#[error("Asset folder path is not a directory.")]
|
||||
AssetFolderNotADirectory(String),
|
||||
#[error("Invalid root path")]
|
||||
InvalidRootPath,
|
||||
#[error("No AssetHandler found for the given extension.")]
|
||||
MissingAssetHandler,
|
||||
#[error("No AssetLoader found for the given extension.")]
|
||||
MissingAssetLoader,
|
||||
#[error("Encountered an error while loading an asset.")]
|
||||
AssetLoadError(#[from] AssetLoadError),
|
||||
#[error("Encountered an io error.")]
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
struct LoaderThread {
|
||||
// NOTE: these must remain private. the LoaderThread Arc counters are used to determine thread liveness
|
||||
// if there is one reference, the loader thread is dead. if there are two references, the loader thread is active
|
||||
requests: Arc<RwLock<Vec<LoadRequest>>>,
|
||||
}
|
||||
|
||||
pub struct AssetServer {
|
||||
asset_folders: Vec<String>,
|
||||
loader_threads: RwLock<Vec<LoaderThread>>,
|
||||
max_loader_threads: usize,
|
||||
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!
|
||||
loaders: Vec<Resources>,
|
||||
extension_to_handler_index: HashMap<String, usize>,
|
||||
extension_to_loader_index: HashMap<String, usize>,
|
||||
}
|
||||
|
||||
impl Default for AssetServer {
|
||||
fn default() -> Self {
|
||||
AssetServer {
|
||||
max_loader_threads: 4,
|
||||
asset_folders: Vec::new(),
|
||||
loader_threads: RwLock::new(Vec::new()),
|
||||
asset_handlers: Arc::new(RwLock::new(Vec::new())),
|
||||
loaders: Vec::new(),
|
||||
extension_to_handler_index: HashMap::new(),
|
||||
extension_to_loader_index: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AssetServer {
|
||||
pub fn add_handler<T>(&mut self, asset_handler: T)
|
||||
where
|
||||
T: AssetLoadRequestHandler,
|
||||
{
|
||||
let mut asset_handlers = self.asset_handlers.write().expect("RwLock poisoned");
|
||||
let handler_index = asset_handlers.len();
|
||||
for extension in asset_handler.extensions().iter() {
|
||||
self.extension_to_handler_index
|
||||
.insert(extension.to_string(), handler_index);
|
||||
}
|
||||
|
||||
asset_handlers.push(Box::new(asset_handler));
|
||||
}
|
||||
|
||||
pub fn add_loader<TLoader, TAsset>(&mut self, loader: TLoader)
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
TAsset: 'static,
|
||||
{
|
||||
let loader_index = self.loaders.len();
|
||||
for extension in loader.extensions().iter() {
|
||||
self.extension_to_loader_index
|
||||
.insert(extension.to_string(), loader_index);
|
||||
}
|
||||
|
||||
let mut resources = Resources::default();
|
||||
resources.insert::<Box<dyn AssetLoader<TAsset>>>(Box::new(loader));
|
||||
self.loaders.push(resources);
|
||||
}
|
||||
|
||||
pub fn add_asset_folder(&mut self, path: &str) {
|
||||
self.asset_folders.push(path.to_string());
|
||||
}
|
||||
|
||||
pub fn get_root_path(&self) -> Result<String, AssetServerError> {
|
||||
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
|
||||
Ok(manifest_dir)
|
||||
} else {
|
||||
match env::current_exe() {
|
||||
Ok(exe_path) => exe_path
|
||||
.parent()
|
||||
.ok_or(AssetServerError::InvalidRootPath)
|
||||
.and_then(|exe_parent_path| {
|
||||
exe_parent_path
|
||||
.to_str()
|
||||
.map(|path| path.to_string())
|
||||
.ok_or(AssetServerError::InvalidRootPath)
|
||||
}),
|
||||
Err(err) => Err(AssetServerError::Io(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_assets(&self) -> Result<(), AssetServerError> {
|
||||
let root_path_str = self.get_root_path()?;
|
||||
let root_path = Path::new(&root_path_str);
|
||||
for folder in self.asset_folders.iter() {
|
||||
let asset_folder_path = root_path.join(folder);
|
||||
self.load_assets_in_folder_recursive(&asset_folder_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load<T>(&self, path: &str) -> Result<Handle<T>, AssetServerError> {
|
||||
self.load_untyped(path)
|
||||
.map(|handle_id| Handle::from(handle_id))
|
||||
}
|
||||
|
||||
pub fn load_sync<T>(
|
||||
&self,
|
||||
assets: &mut Assets<T>,
|
||||
path: &str,
|
||||
) -> Result<Handle<T>, AssetServerError>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let asset_path = AssetPath::from(path);
|
||||
if let Some(ref extension) = asset_path.extension {
|
||||
if let Some(index) = self.extension_to_loader_index.get(extension.as_ref()) {
|
||||
let handle_id = HandleId::new();
|
||||
let resources = &self.loaders[*index];
|
||||
let loader = resources.get::<Box<dyn AssetLoader<T>>>().unwrap();
|
||||
let asset = loader.load_from_file(&asset_path)?;
|
||||
let handle = Handle::from(handle_id);
|
||||
assets.add_with_handle(handle, asset);
|
||||
assets.set_path(handle, &asset_path.path);
|
||||
Ok(handle)
|
||||
} else {
|
||||
Err(AssetServerError::MissingAssetHandler)
|
||||
}
|
||||
} else {
|
||||
Err(AssetServerError::MissingAssetHandler)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_untyped(&self, path: &str) -> Result<HandleId, AssetServerError> {
|
||||
let asset_path = AssetPath::from(path);
|
||||
if let Some(ref extension) = asset_path.extension {
|
||||
if let Some(index) = self.extension_to_handler_index.get(extension.as_ref()) {
|
||||
let handle_id = HandleId::new();
|
||||
self.send_request_to_loader_thread(LoadRequest {
|
||||
handle_id,
|
||||
path: asset_path,
|
||||
handler_index: *index,
|
||||
});
|
||||
Ok(handle_id)
|
||||
} else {
|
||||
Err(AssetServerError::MissingAssetHandler)
|
||||
}
|
||||
} else {
|
||||
Err(AssetServerError::MissingAssetHandler)
|
||||
}
|
||||
}
|
||||
|
||||
fn send_request_to_loader_thread(&self, load_request: LoadRequest) {
|
||||
let mut loader_threads = self.loader_threads.write().expect("RwLock poisoned");
|
||||
if loader_threads.len() < self.max_loader_threads {
|
||||
let loader_thread = LoaderThread {
|
||||
requests: Arc::new(RwLock::new(vec![load_request])),
|
||||
};
|
||||
let requests = loader_thread.requests.clone();
|
||||
loader_threads.push(loader_thread);
|
||||
Self::start_thread(self.asset_handlers.clone(), requests);
|
||||
} else {
|
||||
let most_free_thread = loader_threads
|
||||
.iter()
|
||||
.min_by_key(|l| l.requests.read().expect("RwLock poisoned").len())
|
||||
.unwrap();
|
||||
let mut requests = most_free_thread.requests.write().expect("RwLock poisoned");
|
||||
requests.push(load_request);
|
||||
// if most free thread only has one reference, the thread as spun down. if so, we need to spin it back up!
|
||||
if Arc::strong_count(&most_free_thread.requests) == 1 {
|
||||
Self::start_thread(
|
||||
self.asset_handlers.clone(),
|
||||
most_free_thread.requests.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_thread(
|
||||
request_handlers: Arc<RwLock<Vec<Box<dyn AssetLoadRequestHandler>>>>,
|
||||
requests: Arc<RwLock<Vec<LoadRequest>>>,
|
||||
) {
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
let request = {
|
||||
let mut current_requests = requests.write().expect("RwLock poisoned");
|
||||
if current_requests.len() == 0 {
|
||||
// if there are no requests, spin down the thread
|
||||
break;
|
||||
}
|
||||
|
||||
current_requests.pop().unwrap()
|
||||
};
|
||||
|
||||
let handlers = request_handlers.read().expect("RwLock poisoned");
|
||||
let request_handler = &handlers[request.handler_index];
|
||||
request_handler.handle_request(&request);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn load_assets_in_folder_recursive(&self, path: &Path) -> Result<(), AssetServerError> {
|
||||
if !path.is_dir() {
|
||||
return Err(AssetServerError::AssetFolderNotADirectory(
|
||||
path.to_str().unwrap().to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
for entry in fs::read_dir(&path)? {
|
||||
let entry = entry?;
|
||||
let child_path = entry.path();
|
||||
if !child_path.is_dir() {
|
||||
let _ =
|
||||
self.load_untyped(child_path.to_str().expect("Path should be a valid string"));
|
||||
} else {
|
||||
self.load_assets_in_folder_recursive(&child_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
147
crates/bevy_asset/src/assets.rs
Normal file
147
crates/bevy_asset/src/assets.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
use crate::{
|
||||
update_asset_storage_system, AssetChannel, AssetLoader, AssetServer, ChannelAssetHandler,
|
||||
Handle, HandleId, DEFAULT_HANDLE_ID,
|
||||
};
|
||||
use bevy_app::{stage, AppBuilder, Events};
|
||||
use bevy_core::bytes::GetBytes;
|
||||
use legion::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub enum AssetEvent<T> {
|
||||
Created { handle: Handle<T> },
|
||||
}
|
||||
|
||||
pub struct Assets<T> {
|
||||
assets: HashMap<HandleId, T>,
|
||||
paths: HashMap<String, Handle<T>>,
|
||||
events: Events<AssetEvent<T>>,
|
||||
}
|
||||
|
||||
impl<T> Default for Assets<T> {
|
||||
fn default() -> Self {
|
||||
Assets {
|
||||
assets: HashMap::default(),
|
||||
paths: HashMap::default(),
|
||||
events: Events::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Assets<T> {
|
||||
pub fn get_with_path(&mut self, path: &str) -> Option<Handle<T>> {
|
||||
self.paths.get(path).map(|handle| *handle)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, asset: T) -> Handle<T> {
|
||||
let id = HandleId::new();
|
||||
self.assets.insert(id, asset);
|
||||
let handle = Handle::from_id(id);
|
||||
self.events.send(AssetEvent::Created { handle });
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn add_with_handle(&mut self, handle: Handle<T>, asset: T) {
|
||||
self.assets.insert(handle.id, asset);
|
||||
self.events.send(AssetEvent::Created { handle });
|
||||
}
|
||||
|
||||
pub fn add_default(&mut self, asset: T) -> Handle<T> {
|
||||
self.assets.insert(DEFAULT_HANDLE_ID, asset);
|
||||
let handle = Handle::default();
|
||||
self.events.send(AssetEvent::Created { handle });
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn set_path(&mut self, handle: Handle<T>, path: &str) {
|
||||
self.paths.insert(path.to_string(), handle);
|
||||
}
|
||||
|
||||
pub fn get_id(&self, id: HandleId) -> Option<&T> {
|
||||
self.assets.get(&id)
|
||||
}
|
||||
|
||||
pub fn get_id_mut(&mut self, id: HandleId) -> Option<&mut T> {
|
||||
self.assets.get_mut(&id)
|
||||
}
|
||||
|
||||
pub fn get(&self, handle: &Handle<T>) -> Option<&T> {
|
||||
self.assets.get(&handle.id)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, handle: &Handle<T>) -> Option<&mut T> {
|
||||
self.assets.get_mut(&handle.id)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Handle<T>, &T)> {
|
||||
self.assets.iter().map(|(k, v)| (Handle::from_id(*k), v))
|
||||
}
|
||||
|
||||
pub fn asset_event_system(
|
||||
mut events: ResMut<Events<AssetEvent<T>>>,
|
||||
mut assets: ResMut<Assets<T>>,
|
||||
) {
|
||||
events.extend(assets.events.drain())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> GetBytes for Handle<T> {
|
||||
fn get_bytes(&self) -> Vec<u8> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn get_bytes_ref(&self) -> Option<&[u8]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AddAsset {
|
||||
fn add_asset<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Send + Sync + 'static;
|
||||
fn add_asset_loader<TLoader, TAsset>(&mut self, loader: TLoader) -> &mut Self
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + Clone,
|
||||
TAsset: Send + Sync + 'static;
|
||||
}
|
||||
|
||||
impl AddAsset for AppBuilder {
|
||||
fn add_asset<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.init_resource::<Assets<T>>()
|
||||
.add_system_to_stage(
|
||||
stage::EVENT_UPDATE,
|
||||
Assets::<T>::asset_event_system.system(),
|
||||
)
|
||||
.add_event::<AssetEvent<T>>()
|
||||
}
|
||||
|
||||
fn add_asset_loader<TLoader, TAsset>(&mut self, loader: TLoader) -> &mut Self
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + Clone,
|
||||
TAsset: Send + Sync + 'static,
|
||||
{
|
||||
{
|
||||
if !self.resources().contains::<AssetChannel<TAsset>>() {
|
||||
self.resources_mut().insert(AssetChannel::<TAsset>::new());
|
||||
self.add_system_to_stage(
|
||||
crate::stage::LOAD_ASSETS,
|
||||
update_asset_storage_system::<TAsset>.system(),
|
||||
);
|
||||
}
|
||||
let asset_channel = self
|
||||
.resources()
|
||||
.get::<AssetChannel<TAsset>>()
|
||||
.expect("AssetChannel should always exist at this point.");
|
||||
let mut asset_server = self
|
||||
.resources()
|
||||
.get_mut::<AssetServer>()
|
||||
.expect("AssetServer does not exist. Consider adding it as a resource.");
|
||||
asset_server.add_loader(loader.clone());
|
||||
let handler = ChannelAssetHandler::new(loader, asset_channel.sender.clone());
|
||||
asset_server.add_handler(handler);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
|
@ -63,6 +63,41 @@ impl<T> Handle<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> From<HandleId> for Handle<T> {
|
||||
fn from(value: HandleId) -> Self {
|
||||
Handle::from_id(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<u128> for Handle<T> {
|
||||
fn from(value: u128) -> Self {
|
||||
Handle::from_u128(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<[u8; 16]> for Handle<T> {
|
||||
fn from(value: [u8; 16]) -> Self {
|
||||
Handle::from_bytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<HandleUntyped> for Handle<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn from(handle: HandleUntyped) -> Self {
|
||||
if TypeId::of::<T>() == handle.type_id {
|
||||
Handle {
|
||||
id: handle.id,
|
||||
marker: PhantomData::default(),
|
||||
}
|
||||
} else {
|
||||
panic!("attempted to convert untyped handle to incorrect typed handle")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T> Hash for Handle<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
|
@ -125,14 +160,4 @@ where
|
|||
type_id: TypeId::of::<T>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<HandleUntyped> for Handle<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn from(handle: HandleUntyped) -> Self {
|
||||
Handle::from_untyped(handle)
|
||||
.expect("attempted to convert untyped handle to incorrect typed handle")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,114 +1,28 @@
|
|||
mod assets;
|
||||
mod handle;
|
||||
mod loader;
|
||||
mod asset_server;
|
||||
mod load_request;
|
||||
mod asset_path;
|
||||
|
||||
pub use assets::*;
|
||||
pub use handle::*;
|
||||
pub use loader::*;
|
||||
pub use asset_server::*;
|
||||
pub use load_request::*;
|
||||
pub use asset_path::*;
|
||||
|
||||
use bevy_app::{stage, AppBuilder, Events};
|
||||
use bevy_core::bytes::GetBytes;
|
||||
use legion::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use bevy_app::{AppBuilder, AppPlugin};
|
||||
|
||||
pub enum AssetEvent<T> {
|
||||
Created { handle: Handle<T> },
|
||||
pub mod stage {
|
||||
pub const LOAD_ASSETS: &str = "load_assets";
|
||||
}
|
||||
|
||||
pub struct Assets<T> {
|
||||
assets: HashMap<HandleId, T>,
|
||||
names: HashMap<String, Handle<T>>,
|
||||
events: Events<AssetEvent<T>>,
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct AssetPlugin;
|
||||
|
||||
impl<T> Default for Assets<T> {
|
||||
fn default() -> Self {
|
||||
Assets {
|
||||
assets: HashMap::default(),
|
||||
names: HashMap::default(),
|
||||
events: Events::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Assets<T> {
|
||||
pub fn get_named(&mut self, name: &str) -> Option<Handle<T>> {
|
||||
self.names.get(name).map(|handle| *handle)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, asset: T) -> Handle<T> {
|
||||
let id = HandleId::new();
|
||||
self.assets.insert(id, asset);
|
||||
let handle = Handle::from_id(id);
|
||||
self.events.send(AssetEvent::Created { handle });
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn add_with_handle(&mut self, handle: Handle<T>, asset: T) {
|
||||
self.assets.insert(handle.id, asset);
|
||||
self.events.send(AssetEvent::Created { handle });
|
||||
}
|
||||
|
||||
pub fn add_default(&mut self, asset: T) -> Handle<T> {
|
||||
self.assets.insert(DEFAULT_HANDLE_ID, asset);
|
||||
let handle = Handle::default();
|
||||
self.events.send(AssetEvent::Created { handle });
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: &str, handle: Handle<T>) {
|
||||
self.names.insert(name.to_string(), handle);
|
||||
}
|
||||
|
||||
pub fn get_id(&self, id: HandleId) -> Option<&T> {
|
||||
self.assets.get(&id)
|
||||
}
|
||||
|
||||
pub fn get_id_mut(&mut self, id: HandleId) -> Option<&mut T> {
|
||||
self.assets.get_mut(&id)
|
||||
}
|
||||
|
||||
pub fn get(&self, handle: &Handle<T>) -> Option<&T> {
|
||||
self.assets.get(&handle.id)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, handle: &Handle<T>) -> Option<&mut T> {
|
||||
self.assets.get_mut(&handle.id)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Handle<T>, &T)> {
|
||||
self.assets.iter().map(|(k, v)| (Handle::from_id(*k), v))
|
||||
}
|
||||
|
||||
pub fn asset_event_system(
|
||||
mut events: ResMut<Events<AssetEvent<T>>>,
|
||||
mut assets: ResMut<Assets<T>>,
|
||||
) {
|
||||
events.extend(assets.events.drain())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> GetBytes for Handle<T> {
|
||||
fn get_bytes(&self) -> Vec<u8> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn get_bytes_ref(&self) -> Option<&[u8]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AddAsset {
|
||||
fn add_asset<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Send + Sync + 'static;
|
||||
}
|
||||
|
||||
impl AddAsset for AppBuilder {
|
||||
fn add_asset<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.init_resource::<Assets<T>>()
|
||||
.add_system_to_stage(
|
||||
stage::EVENT_UPDATE,
|
||||
Assets::<T>::asset_event_system.system(),
|
||||
)
|
||||
.add_event::<AssetEvent<T>>()
|
||||
impl AppPlugin for AssetPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
app.add_stage(stage::LOAD_ASSETS).init_resource::<AssetServer>();
|
||||
}
|
||||
}
|
||||
|
|
64
crates/bevy_asset/src/load_request.rs
Normal file
64
crates/bevy_asset/src/load_request.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use crate::{AssetLoadError, AssetLoader, AssetPath, AssetResult, Handle, HandleId};
|
||||
use anyhow::Result;
|
||||
use crossbeam_channel::Sender;
|
||||
use fs::File;
|
||||
use io::Read;
|
||||
use std::{fs, io, path::Path};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoadRequest {
|
||||
pub path: AssetPath,
|
||||
pub handle_id: HandleId,
|
||||
pub handler_index: usize,
|
||||
}
|
||||
|
||||
pub trait AssetLoadRequestHandler: Send + Sync + 'static {
|
||||
fn handle_request(&self, load_request: &LoadRequest);
|
||||
fn extensions(&self) -> &[&str];
|
||||
}
|
||||
|
||||
pub struct ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
{
|
||||
sender: Sender<AssetResult<TAsset>>,
|
||||
loader: TLoader,
|
||||
}
|
||||
|
||||
impl<TLoader, TAsset> ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
{
|
||||
pub fn new(loader: TLoader, sender: Sender<AssetResult<TAsset>>) -> Self {
|
||||
ChannelAssetHandler { sender, loader }
|
||||
}
|
||||
|
||||
fn load_asset(&self, load_request: &LoadRequest) -> Result<TAsset, AssetLoadError> {
|
||||
let mut file = File::open(Path::new(load_request.path.path.as_ref()))?;
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes)?;
|
||||
let asset = self.loader.from_bytes(&load_request.path, bytes)?;
|
||||
Ok(asset)
|
||||
}
|
||||
}
|
||||
|
||||
impl<TLoader, TAsset> AssetLoadRequestHandler for ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + 'static,
|
||||
TAsset: Send + 'static,
|
||||
{
|
||||
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),
|
||||
result,
|
||||
path: load_request.path.clone(),
|
||||
};
|
||||
self.sender
|
||||
.send(asset_result)
|
||||
.expect("loaded asset should have been sent");
|
||||
}
|
||||
fn extensions(&self) -> &[&str] {
|
||||
self.loader.extensions()
|
||||
}
|
||||
}
|
65
crates/bevy_asset/src/loader.rs
Normal file
65
crates/bevy_asset/src/loader.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use crate::{AssetPath, Assets, Handle};
|
||||
use anyhow::Result;
|
||||
use crossbeam_channel::{Receiver, Sender, TryRecvError};
|
||||
use fs::File;
|
||||
use io::Read;
|
||||
use legion::prelude::{Res, ResMut};
|
||||
use std::{fs, io, path::Path};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AssetLoadError {
|
||||
#[error("Encountered an io error while loading asset.")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("This asset's loader encountered an error while loading.")]
|
||||
LoaderError(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub trait AssetLoader<T>: Send + Sync + 'static {
|
||||
fn from_bytes(&self, asset_path: &AssetPath, bytes: Vec<u8>) -> Result<T, anyhow::Error>;
|
||||
fn extensions(&self) -> &[&str];
|
||||
fn load_from_file(&self, asset_path: &AssetPath) -> Result<T, AssetLoadError> {
|
||||
let mut file = File::open(Path::new(asset_path.path.as_ref()))?;
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes)?;
|
||||
let asset = self.from_bytes(asset_path, bytes)?;
|
||||
Ok(asset)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AssetResult<T> {
|
||||
pub result: Result<T, AssetLoadError>,
|
||||
pub handle: Handle<T>,
|
||||
pub path: AssetPath,
|
||||
}
|
||||
|
||||
pub struct AssetChannel<T> {
|
||||
pub sender: Sender<AssetResult<T>>,
|
||||
pub receiver: Receiver<AssetResult<T>>,
|
||||
}
|
||||
|
||||
impl<T> AssetChannel<T> {
|
||||
pub fn new() -> Self {
|
||||
let (sender, receiver) = crossbeam_channel::unbounded();
|
||||
AssetChannel { sender, receiver }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_asset_storage_system<T>(
|
||||
asset_channel: Res<AssetChannel<T>>,
|
||||
mut assets: ResMut<Assets<T>>,
|
||||
) {
|
||||
loop {
|
||||
match asset_channel.receiver.try_recv() {
|
||||
Ok(result) => {
|
||||
let asset = result.result.unwrap();
|
||||
assets.add_with_handle(result.handle, asset);
|
||||
assets.set_path(result.handle, &result.path.path);
|
||||
}
|
||||
Err(TryRecvError::Empty) => {
|
||||
break;
|
||||
}
|
||||
Err(TryRecvError::Disconnected) => panic!("AssetChannel disconnected"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,8 +5,10 @@ authors = ["Carter Anderson <mcanders1@gmail.com>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app" }
|
||||
bevy_asset = { path = "../bevy_asset" }
|
||||
bevy_render = { path = "../bevy_render" }
|
||||
|
||||
gltf = "0.15.2"
|
||||
thiserror = "1.0"
|
||||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
|
@ -1,109 +1,14 @@
|
|||
use bevy_render::{
|
||||
mesh::{Mesh, VertexAttribute, VertexAttributeValues},
|
||||
pipeline::state_descriptors::PrimitiveTopology,
|
||||
};
|
||||
use gltf::{buffer::Source, iter, mesh::Mode};
|
||||
use std::{fs, io, path::Path};
|
||||
use thiserror::Error;
|
||||
mod loader;
|
||||
pub use loader::*;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum GltfError {
|
||||
#[error("Unsupported primitive mode.")]
|
||||
UnsupportedPrimitive { mode: Mode },
|
||||
#[error("Invalid GLTF file.")]
|
||||
Gltf(#[from] gltf::Error),
|
||||
#[error("Failed to load file.")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Binary buffers not supported yet.")]
|
||||
BinaryBuffersUnsupported,
|
||||
}
|
||||
use bevy_app::{AppPlugin, AppBuilder};
|
||||
use bevy_asset::{AddAsset};
|
||||
|
||||
fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
|
||||
match mode {
|
||||
Mode::Points => Ok(PrimitiveTopology::PointList),
|
||||
Mode::Lines => Ok(PrimitiveTopology::LineList),
|
||||
Mode::LineStrip => Ok(PrimitiveTopology::LineStrip),
|
||||
Mode::Triangles => Ok(PrimitiveTopology::TriangleList),
|
||||
Mode::TriangleStrip => Ok(PrimitiveTopology::TriangleStrip),
|
||||
mode @ _ => Err(GltfError::UnsupportedPrimitive { mode }),
|
||||
#[derive(Default)]
|
||||
pub struct GltfPlugin;
|
||||
|
||||
impl AppPlugin for GltfPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
app.add_asset_loader(GltfLoader);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_gltf(path: &str) -> Result<Option<Mesh>, GltfError> {
|
||||
let path: &Path = path.as_ref();
|
||||
let file = fs::File::open(&path)?;
|
||||
let reader = io::BufReader::new(file);
|
||||
let gltf = gltf::Gltf::from_reader(reader)?;
|
||||
let buffer_data = load_buffers(gltf.buffers(), path)?;
|
||||
for scene in gltf.scenes() {
|
||||
for node in scene.nodes() {
|
||||
return Ok(Some(load_node(&buffer_data, &node, 1)?));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn load_node(buffer_data: &[Vec<u8>], node: &gltf::Node, depth: i32) -> Result<Mesh, GltfError> {
|
||||
if let Some(mesh) = node.mesh() {
|
||||
for primitive in mesh.primitives() {
|
||||
let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()]));
|
||||
let primitive_topology = get_primitive_topology(primitive.mode())?;
|
||||
let mut mesh = Mesh::new(primitive_topology);
|
||||
reader
|
||||
.read_positions()
|
||||
.map(|v| VertexAttribute {
|
||||
name: "Vertex_Position".into(),
|
||||
values: VertexAttributeValues::Float3(v.collect()),
|
||||
})
|
||||
.map(|vertex_attribute| mesh.attributes.push(vertex_attribute));
|
||||
|
||||
reader
|
||||
.read_normals()
|
||||
.map(|v| VertexAttribute {
|
||||
name: "Vertex_Normal".into(),
|
||||
values: VertexAttributeValues::Float3(v.collect()),
|
||||
})
|
||||
.map(|vertex_attribute| mesh.attributes.push(vertex_attribute));
|
||||
|
||||
reader
|
||||
.read_tex_coords(0)
|
||||
.map(|v| VertexAttribute {
|
||||
name: "Vertex_Uv".into(),
|
||||
values: VertexAttributeValues::Float2(v.into_f32().collect()),
|
||||
})
|
||||
.map(|vertex_attribute| mesh.attributes.push(vertex_attribute));
|
||||
|
||||
reader.read_indices().map(|indices| {
|
||||
mesh.indices = Some(indices.into_u32().collect::<Vec<u32>>());
|
||||
});
|
||||
|
||||
return Ok(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.children() {
|
||||
return Ok(load_node(buffer_data, &child, depth + 1)?);
|
||||
}
|
||||
|
||||
panic!("failed to find mesh")
|
||||
}
|
||||
|
||||
fn load_buffers(buffers: iter::Buffers, path: &Path) -> Result<Vec<Vec<u8>>, GltfError> {
|
||||
let mut buffer_data = Vec::new();
|
||||
for buffer in buffers {
|
||||
match buffer.source() {
|
||||
Source::Uri(uri) => {
|
||||
if uri.starts_with("data:") {
|
||||
} else {
|
||||
let buffer_path = path.parent().unwrap().join(uri);
|
||||
let buffer_bytes = fs::read(buffer_path)?;
|
||||
buffer_data.push(buffer_bytes);
|
||||
}
|
||||
}
|
||||
Source::Bin => return Err(GltfError::BinaryBuffersUnsupported),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buffer_data)
|
||||
}
|
||||
}
|
127
crates/bevy_gltf/src/loader.rs
Normal file
127
crates/bevy_gltf/src/loader.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
use bevy_render::{
|
||||
mesh::{Mesh, VertexAttribute, VertexAttributeValues},
|
||||
pipeline::state_descriptors::PrimitiveTopology,
|
||||
};
|
||||
|
||||
use bevy_asset::{AssetLoader, AssetPath};
|
||||
use gltf::{buffer::Source, iter, mesh::Mode};
|
||||
use std::{fs, io, path::Path};
|
||||
use thiserror::Error;
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GltfLoader;
|
||||
|
||||
impl AssetLoader<Mesh> for GltfLoader {
|
||||
fn from_bytes(&self, asset_path: &AssetPath, bytes: Vec<u8>) -> Result<Mesh> {
|
||||
let mesh = load_gltf(asset_path, bytes)?;
|
||||
Ok(mesh)
|
||||
}
|
||||
fn extensions(&self) -> &[&str] {
|
||||
static EXTENSIONS: &[&str] = &[
|
||||
"gltf"
|
||||
];
|
||||
EXTENSIONS
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum GltfError {
|
||||
#[error("Unsupported primitive mode.")]
|
||||
UnsupportedPrimitive { mode: Mode },
|
||||
#[error("Invalid GLTF file.")]
|
||||
Gltf(#[from] gltf::Error),
|
||||
#[error("Failed to load file.")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Binary buffers not supported yet.")]
|
||||
BinaryBuffersUnsupported,
|
||||
}
|
||||
|
||||
fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
|
||||
match mode {
|
||||
Mode::Points => Ok(PrimitiveTopology::PointList),
|
||||
Mode::Lines => Ok(PrimitiveTopology::LineList),
|
||||
Mode::LineStrip => Ok(PrimitiveTopology::LineStrip),
|
||||
Mode::Triangles => Ok(PrimitiveTopology::TriangleList),
|
||||
Mode::TriangleStrip => Ok(PrimitiveTopology::TriangleStrip),
|
||||
mode @ _ => Err(GltfError::UnsupportedPrimitive { mode }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_gltf(asset_path: &AssetPath, bytes: Vec<u8>) -> Result<Mesh, GltfError> {
|
||||
let gltf = gltf::Gltf::from_slice(&bytes)?;
|
||||
let buffer_data = load_buffers(gltf.buffers(), asset_path)?;
|
||||
for scene in gltf.scenes() {
|
||||
for node in scene.nodes() {
|
||||
return Ok(load_node(&buffer_data, &node, 1)?);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this when full gltf support is added
|
||||
panic!("no mesh found!")
|
||||
}
|
||||
|
||||
fn load_node(buffer_data: &[Vec<u8>], node: &gltf::Node, depth: i32) -> Result<Mesh, GltfError> {
|
||||
if let Some(mesh) = node.mesh() {
|
||||
for primitive in mesh.primitives() {
|
||||
let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()]));
|
||||
let primitive_topology = get_primitive_topology(primitive.mode())?;
|
||||
let mut mesh = Mesh::new(primitive_topology);
|
||||
reader
|
||||
.read_positions()
|
||||
.map(|v| VertexAttribute {
|
||||
name: "Vertex_Position".into(),
|
||||
values: VertexAttributeValues::Float3(v.collect()),
|
||||
})
|
||||
.map(|vertex_attribute| mesh.attributes.push(vertex_attribute));
|
||||
|
||||
reader
|
||||
.read_normals()
|
||||
.map(|v| VertexAttribute {
|
||||
name: "Vertex_Normal".into(),
|
||||
values: VertexAttributeValues::Float3(v.collect()),
|
||||
})
|
||||
.map(|vertex_attribute| mesh.attributes.push(vertex_attribute));
|
||||
|
||||
reader
|
||||
.read_tex_coords(0)
|
||||
.map(|v| VertexAttribute {
|
||||
name: "Vertex_Uv".into(),
|
||||
values: VertexAttributeValues::Float2(v.into_f32().collect()),
|
||||
})
|
||||
.map(|vertex_attribute| mesh.attributes.push(vertex_attribute));
|
||||
|
||||
reader.read_indices().map(|indices| {
|
||||
mesh.indices = Some(indices.into_u32().collect::<Vec<u32>>());
|
||||
});
|
||||
|
||||
return Ok(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.children() {
|
||||
return Ok(load_node(buffer_data, &child, depth + 1)?);
|
||||
}
|
||||
|
||||
panic!("failed to find mesh")
|
||||
}
|
||||
|
||||
fn load_buffers(buffers: iter::Buffers, asset_path: &AssetPath) -> Result<Vec<Vec<u8>>, GltfError> {
|
||||
let mut buffer_data = Vec::new();
|
||||
for buffer in buffers {
|
||||
match buffer.source() {
|
||||
Source::Uri(uri) => {
|
||||
if uri.starts_with("data:") {
|
||||
} else {
|
||||
let path = Path::new(asset_path.path.as_ref());
|
||||
let buffer_path = path.parent().unwrap().join(uri);
|
||||
let buffer_bytes = fs::read(buffer_path)?;
|
||||
buffer_data.push(buffer_bytes);
|
||||
}
|
||||
}
|
||||
Source::Bin => return Err(GltfError::BinaryBuffersUnsupported),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buffer_data)
|
||||
}
|
|
@ -43,7 +43,7 @@ uuid = { version = "0.8", features = ["v4", "serde"] }
|
|||
tracing = "0.1"
|
||||
itertools = "0.8"
|
||||
rayon = "1.2"
|
||||
crossbeam-channel = "0.4.0"
|
||||
crossbeam-channel = "0.4.2"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
|
|
|
@ -24,7 +24,7 @@ downcast-rs = "1.0"
|
|||
itertools = "0.8"
|
||||
rayon = { version = "1.2", optional = true }
|
||||
crossbeam-queue = { version = "0.2.0", optional = true }
|
||||
crossbeam-channel = "0.4.0"
|
||||
crossbeam-channel = "0.4.2"
|
||||
derivative = "1"
|
||||
smallvec = "1.2"
|
||||
tracing = "0.1"
|
||||
|
|
|
@ -319,11 +319,12 @@ pub fn mesh_specializer_system() -> Box<dyn Schedulable> {
|
|||
)
|
||||
.build(|_, world, meshes, query| {
|
||||
for (mesh_handle, mut renderable) in query.iter_mut(world) {
|
||||
let mesh = meshes.get(&mesh_handle).unwrap();
|
||||
renderable
|
||||
.render_resource_assignments
|
||||
.pipeline_specialization
|
||||
.primitive_topology = mesh.primitive_topology;
|
||||
if let Some(mesh) = meshes.get(&mesh_handle) {
|
||||
renderable
|
||||
.render_resource_assignments
|
||||
.pipeline_specialization
|
||||
.primitive_topology = mesh.primitive_topology;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use bevy::{gltf, prelude::*};
|
||||
use bevy::prelude::*;
|
||||
use bevy_asset::AssetServer;
|
||||
|
||||
fn main() {
|
||||
App::build()
|
||||
|
@ -9,12 +10,14 @@ fn main() {
|
|||
|
||||
fn setup(
|
||||
command_buffer: &mut CommandBuffer,
|
||||
asset_server: ResMut<AssetServer>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// load the mesh
|
||||
let mesh = gltf::load_gltf("assets/models/monkey/Monkey.gltf").unwrap().unwrap();
|
||||
let mesh_handle = meshes.add(mesh);
|
||||
let mesh_handle = asset_server
|
||||
.load_sync(&mut meshes, "assets/models/monkey/Monkey.gltf")
|
||||
.unwrap();
|
||||
|
||||
// create a material for the mesh
|
||||
let material_handle = materials.add(StandardMaterial {
|
||||
|
|
13
examples/asset/load_asset_folder.rs
Normal file
13
examples/asset/load_asset_folder.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use bevy::{asset::AssetServer, prelude::*};
|
||||
|
||||
fn main() {
|
||||
App::build()
|
||||
.add_default_plugins()
|
||||
.add_startup_system(setup.system())
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut asset_server: ResMut<AssetServer>) {
|
||||
asset_server.add_asset_folder("assets");
|
||||
asset_server.load_assets().expect("Assets should exist");
|
||||
}
|
|
@ -18,6 +18,9 @@ impl AddDefaultPlugins for AppBuilder {
|
|||
#[cfg(feature = "window")]
|
||||
self.add_plugin(bevy_window::WindowPlugin::default());
|
||||
|
||||
#[cfg(feature = "asset")]
|
||||
self.add_plugin(bevy_asset::AssetPlugin::default());
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
self.add_plugin(bevy_render::RenderPlugin::default());
|
||||
|
||||
|
@ -27,6 +30,9 @@ impl AddDefaultPlugins for AppBuilder {
|
|||
#[cfg(feature = "ui")]
|
||||
self.add_plugin(bevy_ui::UiPlugin::default());
|
||||
|
||||
#[cfg(feature = "gltf")]
|
||||
self.add_plugin(bevy_gltf::GltfPlugin::default());
|
||||
|
||||
#[cfg(feature = "winit")]
|
||||
self.add_plugin(bevy_winit::WinitPlugin::default());
|
||||
#[cfg(not(feature = "winit"))]
|
||||
|
|
Loading…
Reference in a new issue