mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Hot asset reloading
This commit is contained in:
parent
623c8a8d9a
commit
870f715df3
20 changed files with 590 additions and 149 deletions
|
@ -111,6 +111,14 @@ path = "examples/app/headless.rs"
|
||||||
name = "plugin"
|
name = "plugin"
|
||||||
path = "examples/app/plugin.rs"
|
path = "examples/app/plugin.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "hot_asset_reload"
|
||||||
|
path = "examples/asset/hot_asset_reload.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "asset_loading"
|
||||||
|
path = "examples/asset/asset_loading.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "custom_diagnostic"
|
name = "custom_diagnostic"
|
||||||
path = "examples/diagnostics/custom_diagnostic.rs"
|
path = "examples/diagnostics/custom_diagnostic.rs"
|
||||||
|
|
BIN
assets/models/cube/cube.bin
Normal file
BIN
assets/models/cube/cube.bin
Normal file
Binary file not shown.
122
assets/models/cube/cube.gltf
Normal file
122
assets/models/cube/cube.gltf
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
{
|
||||||
|
"asset" : {
|
||||||
|
"generator" : "Khronos glTF Blender I/O v1.1.46",
|
||||||
|
"version" : "2.0"
|
||||||
|
},
|
||||||
|
"scene" : 0,
|
||||||
|
"scenes" : [
|
||||||
|
{
|
||||||
|
"name" : "Scene",
|
||||||
|
"nodes" : [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes" : [
|
||||||
|
{
|
||||||
|
"mesh" : 0,
|
||||||
|
"name" : "Cube"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials" : [
|
||||||
|
{
|
||||||
|
"doubleSided" : true,
|
||||||
|
"emissiveFactor" : [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"name" : "Material",
|
||||||
|
"pbrMetallicRoughness" : {
|
||||||
|
"baseColorFactor" : [
|
||||||
|
0.800000011920929,
|
||||||
|
0.800000011920929,
|
||||||
|
0.800000011920929,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"metallicFactor" : 0,
|
||||||
|
"roughnessFactor" : 0.4000000059604645
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes" : [
|
||||||
|
{
|
||||||
|
"name" : "Cube",
|
||||||
|
"primitives" : [
|
||||||
|
{
|
||||||
|
"attributes" : {
|
||||||
|
"POSITION" : 0,
|
||||||
|
"NORMAL" : 1,
|
||||||
|
"TEXCOORD_0" : 2
|
||||||
|
},
|
||||||
|
"indices" : 3,
|
||||||
|
"material" : 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors" : [
|
||||||
|
{
|
||||||
|
"bufferView" : 0,
|
||||||
|
"componentType" : 5126,
|
||||||
|
"count" : 24,
|
||||||
|
"max" : [
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"min" : [
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"type" : "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView" : 1,
|
||||||
|
"componentType" : 5126,
|
||||||
|
"count" : 24,
|
||||||
|
"type" : "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView" : 2,
|
||||||
|
"componentType" : 5126,
|
||||||
|
"count" : 24,
|
||||||
|
"type" : "VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView" : 3,
|
||||||
|
"componentType" : 5123,
|
||||||
|
"count" : 36,
|
||||||
|
"type" : "SCALAR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews" : [
|
||||||
|
{
|
||||||
|
"buffer" : 0,
|
||||||
|
"byteLength" : 288,
|
||||||
|
"byteOffset" : 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer" : 0,
|
||||||
|
"byteLength" : 288,
|
||||||
|
"byteOffset" : 288
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer" : 0,
|
||||||
|
"byteLength" : 192,
|
||||||
|
"byteOffset" : 576
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer" : 0,
|
||||||
|
"byteLength" : 72,
|
||||||
|
"byteOffset" : 768
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers" : [
|
||||||
|
{
|
||||||
|
"byteLength" : 840,
|
||||||
|
"uri" : "cube.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
assets/models/sphere/sphere.bin
Normal file
BIN
assets/models/sphere/sphere.bin
Normal file
Binary file not shown.
100
assets/models/sphere/sphere.gltf
Normal file
100
assets/models/sphere/sphere.gltf
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
"asset" : {
|
||||||
|
"generator" : "Khronos glTF Blender I/O v1.1.46",
|
||||||
|
"version" : "2.0"
|
||||||
|
},
|
||||||
|
"scene" : 0,
|
||||||
|
"scenes" : [
|
||||||
|
{
|
||||||
|
"name" : "Scene",
|
||||||
|
"nodes" : [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes" : [
|
||||||
|
{
|
||||||
|
"mesh" : 0,
|
||||||
|
"name" : "Sphere"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes" : [
|
||||||
|
{
|
||||||
|
"name" : "Sphere",
|
||||||
|
"primitives" : [
|
||||||
|
{
|
||||||
|
"attributes" : {
|
||||||
|
"POSITION" : 0,
|
||||||
|
"NORMAL" : 1,
|
||||||
|
"TEXCOORD_0" : 2
|
||||||
|
},
|
||||||
|
"indices" : 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors" : [
|
||||||
|
{
|
||||||
|
"bufferView" : 0,
|
||||||
|
"componentType" : 5126,
|
||||||
|
"count" : 2143,
|
||||||
|
"max" : [
|
||||||
|
1.0000005960464478,
|
||||||
|
1,
|
||||||
|
1.000001072883606
|
||||||
|
],
|
||||||
|
"min" : [
|
||||||
|
-1.000000238418579,
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"type" : "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView" : 1,
|
||||||
|
"componentType" : 5126,
|
||||||
|
"count" : 2143,
|
||||||
|
"type" : "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView" : 2,
|
||||||
|
"componentType" : 5126,
|
||||||
|
"count" : 2143,
|
||||||
|
"type" : "VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView" : 3,
|
||||||
|
"componentType" : 5123,
|
||||||
|
"count" : 11904,
|
||||||
|
"type" : "SCALAR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews" : [
|
||||||
|
{
|
||||||
|
"buffer" : 0,
|
||||||
|
"byteLength" : 25716,
|
||||||
|
"byteOffset" : 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer" : 0,
|
||||||
|
"byteLength" : 25716,
|
||||||
|
"byteOffset" : 25716
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer" : 0,
|
||||||
|
"byteLength" : 17144,
|
||||||
|
"byteOffset" : 51432
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer" : 0,
|
||||||
|
"byteLength" : 23808,
|
||||||
|
"byteOffset" : 68576
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers" : [
|
||||||
|
{
|
||||||
|
"byteLength" : 92384,
|
||||||
|
"uri" : "sphere.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -4,6 +4,10 @@ version = "0.1.0"
|
||||||
authors = ["Carter Anderson <mcanders1@gmail.com>"]
|
authors = ["Carter Anderson <mcanders1@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["filesystem_watcher"]
|
||||||
|
filesystem_watcher = ["notify"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_app = { path = "../bevy_app" }
|
bevy_app = { path = "../bevy_app" }
|
||||||
bevy_core = { path = "../bevy_core" }
|
bevy_core = { path = "../bevy_core" }
|
||||||
|
@ -12,4 +16,5 @@ legion = { path = "../bevy_legion" }
|
||||||
uuid = { version = "0.8", features = ["v4", "serde"] }
|
uuid = { version = "0.8", features = ["v4", "serde"] }
|
||||||
crossbeam-channel = "0.4.2"
|
crossbeam-channel = "0.4.2"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
notify = { version = "5.0.0-pre.2", optional = true }
|
|
@ -1,55 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AssetLoadError, AssetLoadRequestHandler, AssetLoader, AssetPath, Assets, Handle, HandleId,
|
filesystem_watcher::FilesystemWatcher, AssetLoadError, AssetLoadRequestHandler, AssetLoader,
|
||||||
LoadRequest,
|
Assets, Handle, HandleId, LoadRequest,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use legion::prelude::Resources;
|
use crossbeam_channel::TryRecvError;
|
||||||
|
use legion::prelude::{Res, Resources};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{HashMap, HashSet},
|
||||||
env, fs, io,
|
env, fs, io,
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
@ -27,6 +28,8 @@ pub enum AssetServerError {
|
||||||
AssetLoadError(#[from] AssetLoadError),
|
AssetLoadError(#[from] AssetLoadError),
|
||||||
#[error("Encountered an io error.")]
|
#[error("Encountered an io error.")]
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
|
#[error("Failed to watch asset folder.")]
|
||||||
|
AssetFolderWatchError { path: PathBuf },
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LoaderThread {
|
struct LoaderThread {
|
||||||
|
@ -36,7 +39,7 @@ struct LoaderThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AssetServer {
|
pub struct AssetServer {
|
||||||
asset_folders: Vec<String>,
|
asset_folders: Vec<PathBuf>,
|
||||||
loader_threads: RwLock<Vec<LoaderThread>>,
|
loader_threads: RwLock<Vec<LoaderThread>>,
|
||||||
max_loader_threads: usize,
|
max_loader_threads: usize,
|
||||||
asset_handlers: Arc<RwLock<Vec<Box<dyn AssetLoadRequestHandler>>>>,
|
asset_handlers: Arc<RwLock<Vec<Box<dyn AssetLoadRequestHandler>>>>,
|
||||||
|
@ -44,6 +47,9 @@ pub struct AssetServer {
|
||||||
loaders: Vec<Resources>,
|
loaders: Vec<Resources>,
|
||||||
extension_to_handler_index: HashMap<String, usize>,
|
extension_to_handler_index: HashMap<String, usize>,
|
||||||
extension_to_loader_index: HashMap<String, usize>,
|
extension_to_loader_index: HashMap<String, usize>,
|
||||||
|
path_to_handle: RwLock<HashMap<PathBuf, HandleId>>,
|
||||||
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
|
filesystem_watcher: Option<FilesystemWatcher>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AssetServer {
|
impl Default for AssetServer {
|
||||||
|
@ -56,6 +62,9 @@ impl Default for AssetServer {
|
||||||
loaders: Vec::new(),
|
loaders: Vec::new(),
|
||||||
extension_to_handler_index: HashMap::new(),
|
extension_to_handler_index: HashMap::new(),
|
||||||
extension_to_loader_index: HashMap::new(),
|
extension_to_loader_index: HashMap::new(),
|
||||||
|
path_to_handle: RwLock::new(HashMap::default()),
|
||||||
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
|
filesystem_watcher: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,63 +100,139 @@ impl AssetServer {
|
||||||
self.loaders.push(resources);
|
self.loaders.push(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_asset_folder(&mut self, path: &str) {
|
pub fn load_asset_folder<P: AsRef<Path>>(&mut self, path: P) -> Result<(), AssetServerError> {
|
||||||
self.asset_folders.push(path.to_string());
|
let root_path = self.get_root_path()?;
|
||||||
|
let asset_folder = root_path.join(path);
|
||||||
|
|
||||||
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
|
Self::watch_folder_for_changes(&mut self.filesystem_watcher, &asset_folder)?;
|
||||||
|
|
||||||
|
self.load_assets_in_folder_recursive(&asset_folder)?;
|
||||||
|
self.asset_folders.push(asset_folder);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_root_path(&self) -> Result<String, AssetServerError> {
|
pub fn get_handle<T, P: AsRef<Path>>(&self, path: P) -> Option<Handle<T>> {
|
||||||
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
|
self.path_to_handle
|
||||||
Ok(manifest_dir)
|
.read()
|
||||||
} else {
|
.expect("RwLock poisoned")
|
||||||
match env::current_exe() {
|
.get(path.as_ref())
|
||||||
Ok(exe_path) => exe_path
|
.map(|h| Handle::from(*h))
|
||||||
.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> {
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
let root_path_str = self.get_root_path()?;
|
fn watch_folder_for_changes<P: AsRef<Path>>(
|
||||||
let root_path = Path::new(&root_path_str);
|
filesystem_watcher: &mut Option<FilesystemWatcher>,
|
||||||
for folder in self.asset_folders.iter() {
|
path: P,
|
||||||
let asset_folder_path = root_path.join(folder);
|
) -> Result<(), AssetServerError> {
|
||||||
self.load_assets_in_folder_recursive(&asset_folder_path)?;
|
if let Some(watcher) = filesystem_watcher {
|
||||||
|
watcher.watch_folder(&path).map_err(|_error| {
|
||||||
|
AssetServerError::AssetFolderWatchError {
|
||||||
|
path: path.as_ref().to_owned(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load<T>(&self, path: &str) -> Result<Handle<T>, AssetServerError> {
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
|
pub fn watch_for_changes(&mut self) -> Result<(), AssetServerError> {
|
||||||
|
let _ = self
|
||||||
|
.filesystem_watcher
|
||||||
|
.get_or_insert_with(|| FilesystemWatcher::default());
|
||||||
|
for asset_folder in self.asset_folders.iter() {
|
||||||
|
Self::watch_folder_for_changes(&mut self.filesystem_watcher, asset_folder)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
|
pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
|
||||||
|
use notify::event::{Event, EventKind, ModifyKind};
|
||||||
|
let mut changed = HashSet::new();
|
||||||
|
loop {
|
||||||
|
if let Some(ref filesystem_watcher) = asset_server.filesystem_watcher {
|
||||||
|
match filesystem_watcher.receiver.try_recv() {
|
||||||
|
Ok(result) => {
|
||||||
|
let event = result.unwrap();
|
||||||
|
match event {
|
||||||
|
Event {
|
||||||
|
kind: EventKind::Modify(ModifyKind::Data(_)),
|
||||||
|
paths,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
for path in paths.iter() {
|
||||||
|
if !changed.contains(path) {
|
||||||
|
let root_path = asset_server.get_root_path().unwrap();
|
||||||
|
let relative_path = path.strip_prefix(root_path).unwrap();
|
||||||
|
match asset_server.load_untyped(relative_path) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(AssetServerError::AssetLoadError(error)) => {
|
||||||
|
panic!("{:?}", error)
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed.extend(paths);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Empty) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Disconnected) => panic!("FilesystemWatcher disconnected"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> {
|
||||||
|
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
|
||||||
|
Ok(PathBuf::from(manifest_dir))
|
||||||
|
} else {
|
||||||
|
match env::current_exe() {
|
||||||
|
Ok(exe_path) => exe_path
|
||||||
|
.parent()
|
||||||
|
.ok_or(AssetServerError::InvalidRootPath)
|
||||||
|
.map(|exe_parent_path| exe_parent_path.to_owned()),
|
||||||
|
Err(err) => Err(AssetServerError::Io(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load<T, P: AsRef<Path>>(&self, path: P) -> Result<Handle<T>, AssetServerError> {
|
||||||
self.load_untyped(path)
|
self.load_untyped(path)
|
||||||
.map(|handle_id| Handle::from(handle_id))
|
.map(|handle_id| Handle::from(handle_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_sync<T>(
|
pub fn load_sync<T, P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
assets: &mut Assets<T>,
|
assets: &mut Assets<T>,
|
||||||
path: &str,
|
path: P,
|
||||||
) -> Result<Handle<T>, AssetServerError>
|
) -> Result<Handle<T>, AssetServerError>
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
let asset_path = AssetPath::from(path);
|
let path = path.as_ref();
|
||||||
if let Some(ref extension) = asset_path.extension {
|
if let Some(ref extension) = path.extension() {
|
||||||
if let Some(index) = self.extension_to_loader_index.get(extension.as_ref()) {
|
if let Some(index) = self.extension_to_loader_index.get(
|
||||||
|
extension
|
||||||
|
.to_str()
|
||||||
|
.expect("extension should be a valid string"),
|
||||||
|
) {
|
||||||
let handle_id = HandleId::new();
|
let handle_id = HandleId::new();
|
||||||
let resources = &self.loaders[*index];
|
let resources = &self.loaders[*index];
|
||||||
let loader = resources.get::<Box<dyn AssetLoader<T>>>().unwrap();
|
let loader = resources.get::<Box<dyn AssetLoader<T>>>().unwrap();
|
||||||
let asset = loader.load_from_file(&asset_path)?;
|
let asset = loader.load_from_file(path)?;
|
||||||
let handle = Handle::from(handle_id);
|
let handle = Handle::from(handle_id);
|
||||||
assets.set(handle, asset);
|
assets.set(handle, asset);
|
||||||
assets.set_path(handle, &asset_path.path);
|
assets.set_path(handle, path);
|
||||||
Ok(handle)
|
Ok(handle)
|
||||||
} else {
|
} else {
|
||||||
Err(AssetServerError::MissingAssetHandler)
|
Err(AssetServerError::MissingAssetHandler)
|
||||||
|
@ -157,14 +242,28 @@ impl AssetServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_untyped(&self, path: &str) -> Result<HandleId, AssetServerError> {
|
pub fn load_untyped<P: AsRef<Path>>(&self, path: P) -> Result<HandleId, AssetServerError> {
|
||||||
let asset_path = AssetPath::from(path);
|
let path = path.as_ref();
|
||||||
if let Some(ref extension) = asset_path.extension {
|
if let Some(ref extension) = path.extension() {
|
||||||
if let Some(index) = self.extension_to_handler_index.get(extension.as_ref()) {
|
if let Some(index) = self.extension_to_handler_index.get(
|
||||||
let handle_id = HandleId::new();
|
extension
|
||||||
|
.to_str()
|
||||||
|
.expect("Extension should be a valid string."),
|
||||||
|
) {
|
||||||
|
let handle_id = {
|
||||||
|
let mut path_to_handle = self.path_to_handle.write().expect("RwLock poisoned");
|
||||||
|
if let Some(handle_id) = path_to_handle.get(path) {
|
||||||
|
*handle_id
|
||||||
|
} else {
|
||||||
|
let handle_id = HandleId::new();
|
||||||
|
path_to_handle.insert(path.to_owned(), handle_id.clone());
|
||||||
|
handle_id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.send_request_to_loader_thread(LoadRequest {
|
self.send_request_to_loader_thread(LoadRequest {
|
||||||
handle_id,
|
handle_id,
|
||||||
path: asset_path,
|
path: path.to_owned(),
|
||||||
handler_index: *index,
|
handler_index: *index,
|
||||||
});
|
});
|
||||||
Ok(handle_id)
|
Ok(handle_id)
|
||||||
|
@ -232,12 +331,14 @@ impl AssetServer {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry in fs::read_dir(&path)? {
|
let root_path = self.get_root_path()?;
|
||||||
|
for entry in fs::read_dir(path)? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let child_path = entry.path();
|
let child_path = entry.path();
|
||||||
if !child_path.is_dir() {
|
if !child_path.is_dir() {
|
||||||
|
let relative_child_path = child_path.strip_prefix(&root_path).unwrap();
|
||||||
let _ =
|
let _ =
|
||||||
self.load_untyped(child_path.to_str().expect("Path should be a valid string"));
|
self.load_untyped(relative_child_path.to_str().expect("Path should be a valid string"));
|
||||||
} else {
|
} else {
|
||||||
self.load_assets_in_folder_recursive(&child_path)?;
|
self.load_assets_in_folder_recursive(&child_path)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
use bevy_app::{stage, AppBuilder, Events};
|
use bevy_app::{stage, AppBuilder, Events};
|
||||||
use bevy_core::bytes::GetBytes;
|
use bevy_core::bytes::GetBytes;
|
||||||
use legion::prelude::*;
|
use legion::prelude::*;
|
||||||
use std::collections::HashMap;
|
use std::{path::{Path, PathBuf}, collections::HashMap};
|
||||||
|
|
||||||
pub enum AssetEvent<T> {
|
pub enum AssetEvent<T> {
|
||||||
Created { handle: Handle<T> },
|
Created { handle: Handle<T> },
|
||||||
|
@ -14,7 +14,7 @@ pub enum AssetEvent<T> {
|
||||||
|
|
||||||
pub struct Assets<T> {
|
pub struct Assets<T> {
|
||||||
assets: HashMap<HandleId, T>,
|
assets: HashMap<HandleId, T>,
|
||||||
paths: HashMap<String, Handle<T>>,
|
paths: HashMap<PathBuf, Handle<T>>,
|
||||||
events: Events<AssetEvent<T>>,
|
events: Events<AssetEvent<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@ impl<T> Default for Assets<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Assets<T> {
|
impl<T> Assets<T> {
|
||||||
pub fn get_with_path(&mut self, path: &str) -> Option<Handle<T>> {
|
pub fn get_with_path<P: AsRef<Path>>(&mut self, path: P) -> Option<Handle<T>> {
|
||||||
self.paths.get(path).map(|handle| *handle)
|
self.paths.get(path.as_ref()).map(|handle| *handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, asset: T) -> Handle<T> {
|
pub fn add(&mut self, asset: T) -> Handle<T> {
|
||||||
|
@ -64,8 +64,8 @@ impl<T> Assets<T> {
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_path(&mut self, handle: Handle<T>, path: &str) {
|
pub fn set_path<P: AsRef<Path>>(&mut self, handle: Handle<T>, path: P) {
|
||||||
self.paths.insert(path.to_string(), handle);
|
self.paths.insert(path.as_ref().to_owned(), handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_id(&self, id: HandleId) -> Option<&T> {
|
pub fn get_id(&self, id: HandleId) -> Option<&T> {
|
||||||
|
|
26
crates/bevy_asset/src/filesystem_watcher.rs
Normal file
26
crates/bevy_asset/src/filesystem_watcher.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
use notify::{Event, RecommendedWatcher, RecursiveMode, Result, Watcher};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Watches for changes to assets on the filesystem and informs the `AssetServer` to reload them
|
||||||
|
pub struct FilesystemWatcher {
|
||||||
|
pub watcher: RecommendedWatcher,
|
||||||
|
pub receiver: Receiver<Result<Event>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FilesystemWatcher {
|
||||||
|
fn default() -> Self {
|
||||||
|
let (sender, receiver) = crossbeam_channel::unbounded();
|
||||||
|
let watcher: RecommendedWatcher = Watcher::new_immediate(move |res| {
|
||||||
|
sender.send(res).expect("Watch event send failure");
|
||||||
|
})
|
||||||
|
.expect("Failed to create filesystem watcher");
|
||||||
|
FilesystemWatcher { watcher, receiver }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilesystemWatcher {
|
||||||
|
pub fn watch_folder<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||||
|
self.watcher.watch(path, RecursiveMode::Recursive)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
mod asset_path;
|
|
||||||
mod asset_server;
|
mod asset_server;
|
||||||
mod assets;
|
mod assets;
|
||||||
mod handle;
|
mod handle;
|
||||||
mod load_request;
|
mod load_request;
|
||||||
mod loader;
|
mod loader;
|
||||||
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
|
pub mod filesystem_watcher;
|
||||||
|
|
||||||
pub use asset_path::*;
|
|
||||||
pub use asset_server::*;
|
pub use asset_server::*;
|
||||||
pub use assets::*;
|
pub use assets::*;
|
||||||
pub use handle::*;
|
pub use handle::*;
|
||||||
|
@ -13,6 +13,7 @@ pub use load_request::*;
|
||||||
pub use loader::*;
|
pub use loader::*;
|
||||||
|
|
||||||
use bevy_app::{AppBuilder, AppPlugin};
|
use bevy_app::{AppBuilder, AppPlugin};
|
||||||
|
use legion::prelude::IntoSystem;
|
||||||
|
|
||||||
pub mod stage {
|
pub mod stage {
|
||||||
pub const LOAD_ASSETS: &str = "load_assets";
|
pub const LOAD_ASSETS: &str = "load_assets";
|
||||||
|
@ -23,7 +24,9 @@ pub struct AssetPlugin;
|
||||||
|
|
||||||
impl AppPlugin for AssetPlugin {
|
impl AppPlugin for AssetPlugin {
|
||||||
fn build(&self, app: &mut AppBuilder) {
|
fn build(&self, app: &mut AppBuilder) {
|
||||||
app.add_stage(stage::LOAD_ASSETS)
|
app.add_stage_before(bevy_app::stage::PRE_UPDATE, stage::LOAD_ASSETS)
|
||||||
.init_resource::<AssetServer>();
|
.init_resource::<AssetServer>();
|
||||||
|
#[cfg(feature = "filesystem_watcher")]
|
||||||
|
app.add_system_to_stage(stage::LOAD_ASSETS, AssetServer::filesystem_watcher_system.system());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::{AssetLoadError, AssetLoader, AssetPath, AssetResult, Handle, HandleId};
|
use crate::{AssetLoadError, AssetLoader, AssetResult, Handle, HandleId};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use fs::File;
|
use fs::File;
|
||||||
use io::Read;
|
use io::Read;
|
||||||
use std::{fs, io, path::Path};
|
use std::{fs, io, path::PathBuf};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LoadRequest {
|
pub struct LoadRequest {
|
||||||
pub path: AssetPath,
|
pub path: PathBuf,
|
||||||
pub handle_id: HandleId,
|
pub handle_id: HandleId,
|
||||||
pub handler_index: usize,
|
pub handler_index: usize,
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_asset(&self, load_request: &LoadRequest) -> Result<TAsset, AssetLoadError> {
|
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 file = File::open(&load_request.path)?;
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
file.read_to_end(&mut bytes)?;
|
file.read_to_end(&mut bytes)?;
|
||||||
let asset = self.loader.from_bytes(&load_request.path, bytes)?;
|
let asset = self.loader.from_bytes(&load_request.path, bytes)?;
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
use crate::{AssetPath, Assets, Handle};
|
use crate::{Assets, Handle};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossbeam_channel::{Receiver, Sender, TryRecvError};
|
use crossbeam_channel::{Receiver, Sender, TryRecvError};
|
||||||
use fs::File;
|
use fs::File;
|
||||||
use io::Read;
|
use io::Read;
|
||||||
use legion::prelude::{Res, ResMut};
|
use legion::prelude::{Res, ResMut};
|
||||||
use std::{fs, io, path::Path};
|
use std::{
|
||||||
|
fs, io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -16,10 +19,10 @@ pub enum AssetLoadError {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AssetLoader<T>: Send + Sync + 'static {
|
pub trait AssetLoader<T>: Send + Sync + 'static {
|
||||||
fn from_bytes(&self, asset_path: &AssetPath, bytes: Vec<u8>) -> Result<T, anyhow::Error>;
|
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<T, anyhow::Error>;
|
||||||
fn extensions(&self) -> &[&str];
|
fn extensions(&self) -> &[&str];
|
||||||
fn load_from_file(&self, asset_path: &AssetPath) -> Result<T, AssetLoadError> {
|
fn load_from_file(&self, asset_path: &Path) -> Result<T, AssetLoadError> {
|
||||||
let mut file = File::open(Path::new(asset_path.path.as_ref()))?;
|
let mut file = File::open(asset_path)?;
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
file.read_to_end(&mut bytes)?;
|
file.read_to_end(&mut bytes)?;
|
||||||
let asset = self.from_bytes(asset_path, bytes)?;
|
let asset = self.from_bytes(asset_path, bytes)?;
|
||||||
|
@ -30,7 +33,7 @@ pub trait AssetLoader<T>: Send + Sync + 'static {
|
||||||
pub struct AssetResult<T> {
|
pub struct AssetResult<T> {
|
||||||
pub result: Result<T, AssetLoadError>,
|
pub result: Result<T, AssetLoadError>,
|
||||||
pub handle: Handle<T>,
|
pub handle: Handle<T>,
|
||||||
pub path: AssetPath,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AssetChannel<T> {
|
pub struct AssetChannel<T> {
|
||||||
|
@ -54,7 +57,7 @@ pub fn update_asset_storage_system<T>(
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let asset = result.result.unwrap();
|
let asset = result.result.unwrap();
|
||||||
assets.set(result.handle, asset);
|
assets.set(result.handle, asset);
|
||||||
assets.set_path(result.handle, &result.path.path);
|
assets.set_path(result.handle, &result.path);
|
||||||
}
|
}
|
||||||
Err(TryRecvError::Empty) => {
|
Err(TryRecvError::Empty) => {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use bevy_render::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bevy_asset::{AssetLoader, AssetPath};
|
use bevy_asset::AssetLoader;
|
||||||
use gltf::{buffer::Source, iter, mesh::Mode};
|
use gltf::{buffer::Source, iter, mesh::Mode};
|
||||||
use std::{fs, io, path::Path};
|
use std::{fs, io, path::Path};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -13,7 +13,7 @@ use thiserror::Error;
|
||||||
pub struct GltfLoader;
|
pub struct GltfLoader;
|
||||||
|
|
||||||
impl AssetLoader<Mesh> for GltfLoader {
|
impl AssetLoader<Mesh> for GltfLoader {
|
||||||
fn from_bytes(&self, asset_path: &AssetPath, bytes: Vec<u8>) -> Result<Mesh> {
|
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh> {
|
||||||
let mesh = load_gltf(asset_path, bytes)?;
|
let mesh = load_gltf(asset_path, bytes)?;
|
||||||
Ok(mesh)
|
Ok(mesh)
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_gltf(asset_path: &AssetPath, bytes: Vec<u8>) -> Result<Mesh, GltfError> {
|
pub fn load_gltf(asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh, GltfError> {
|
||||||
let gltf = gltf::Gltf::from_slice(&bytes)?;
|
let gltf = gltf::Gltf::from_slice(&bytes)?;
|
||||||
let buffer_data = load_buffers(gltf.buffers(), asset_path)?;
|
let buffer_data = load_buffers(gltf.buffers(), asset_path)?;
|
||||||
for scene in gltf.scenes() {
|
for scene in gltf.scenes() {
|
||||||
|
@ -104,15 +104,14 @@ fn load_node(buffer_data: &[Vec<u8>], node: &gltf::Node, depth: i32) -> Result<M
|
||||||
panic!("failed to find mesh")
|
panic!("failed to find mesh")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_buffers(buffers: iter::Buffers, asset_path: &AssetPath) -> Result<Vec<Vec<u8>>, GltfError> {
|
fn load_buffers(buffers: iter::Buffers, asset_path: &Path) -> Result<Vec<Vec<u8>>, GltfError> {
|
||||||
let mut buffer_data = Vec::new();
|
let mut buffer_data = Vec::new();
|
||||||
for buffer in buffers {
|
for buffer in buffers {
|
||||||
match buffer.source() {
|
match buffer.source() {
|
||||||
Source::Uri(uri) => {
|
Source::Uri(uri) => {
|
||||||
if uri.starts_with("data:") {
|
if uri.starts_with("data:") {
|
||||||
} else {
|
} else {
|
||||||
let path = Path::new(asset_path.path.as_ref());
|
let buffer_path = asset_path.parent().unwrap().join(uri);
|
||||||
let buffer_path = path.parent().unwrap().join(uri);
|
|
||||||
let buffer_bytes = fs::read(buffer_path)?;
|
let buffer_bytes = fs::read(buffer_path)?;
|
||||||
buffer_data.push(buffer_bytes);
|
buffer_data.push(buffer_bytes);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ use crate::{
|
||||||
state_descriptors::{IndexFormat, PrimitiveTopology},
|
state_descriptors::{IndexFormat, PrimitiveTopology},
|
||||||
VertexBufferDescriptor, VertexBufferDescriptors, VertexFormat,
|
VertexBufferDescriptor, VertexBufferDescriptors, VertexFormat,
|
||||||
},
|
},
|
||||||
render_resource::{BufferInfo, BufferUsage, EntitiesWaitingForAssets},
|
render_resource::{BufferInfo, BufferUsage},
|
||||||
renderer::{RenderResourceContext, RenderResources},
|
renderer::RenderResources,
|
||||||
shader::AsUniforms,
|
shader::AsUniforms,
|
||||||
Renderable, Vertex,
|
Renderable, Vertex,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use super::Texture;
|
use super::Texture;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bevy_asset::{AssetLoader, AssetPath};
|
use bevy_asset::AssetLoader;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct PngTextureLoader;
|
pub struct PngTextureLoader;
|
||||||
|
|
||||||
impl AssetLoader<Texture> for PngTextureLoader {
|
impl AssetLoader<Texture> for PngTextureLoader {
|
||||||
fn from_bytes(&self, _asset_path: &AssetPath, bytes: Vec<u8>) -> Result<Texture> {
|
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<Texture> {
|
||||||
let decoder = png::Decoder::new(bytes.as_slice());
|
let decoder = png::Decoder::new(bytes.as_slice());
|
||||||
let (info, mut reader) = decoder.read_info()?;
|
let (info, mut reader) = decoder.read_info()?;
|
||||||
let mut data = vec![0; info.buffer_size()];
|
let mut data = vec![0; info.buffer_size()];
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::Font;
|
use crate::Font;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bevy_asset::{AssetLoader, AssetPath};
|
use bevy_asset::AssetLoader;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FontLoader;
|
pub struct FontLoader;
|
||||||
|
|
||||||
impl AssetLoader<Font> for FontLoader {
|
impl AssetLoader<Font> for FontLoader {
|
||||||
fn from_bytes(&self, _asset_path: &AssetPath, bytes: Vec<u8>) -> Result<Font> {
|
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<Font> {
|
||||||
Ok(Font::try_from_bytes(bytes)?)
|
Ok(Font::try_from_bytes(bytes)?)
|
||||||
}
|
}
|
||||||
fn extensions(&self) -> &[&str] {
|
fn extensions(&self) -> &[&str] {
|
||||||
|
|
80
examples/asset/asset_loading.rs
Normal file
80
examples/asset/asset_loading.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_asset::AssetServer;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::build()
|
||||||
|
.add_default_plugins()
|
||||||
|
.add_startup_system(setup.system())
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
command_buffer: &mut CommandBuffer,
|
||||||
|
mut asset_server: ResMut<AssetServer>,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// You can load all assets in a folder like this. they will be loaded in parallel without blocking
|
||||||
|
asset_server.load_asset_folder("assets/models/monkey").unwrap();
|
||||||
|
|
||||||
|
// Then any asset in the folder can be accessed like this:
|
||||||
|
let monkey_handle = asset_server
|
||||||
|
.get_handle("assets/models/monkey/Monkey.gltf")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// You can load individual assets like this:
|
||||||
|
let cube_handle = asset_server
|
||||||
|
.load("assets/models/cube/cube.gltf")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assets are loaded in the background by default, which means they might not be available immediately after loading.
|
||||||
|
// If you need immediate access you can load assets synchronously like this:
|
||||||
|
let sphere_handle = asset_server.load_sync(&mut meshes, "assets/models/sphere/sphere.gltf").unwrap();
|
||||||
|
let sphere = meshes.get(&sphere_handle).unwrap();
|
||||||
|
println!("{:?}", sphere.primitive_topology);
|
||||||
|
|
||||||
|
// You can also add assets directly to their Assets<T> storage
|
||||||
|
let material_handle = materials.add(StandardMaterial {
|
||||||
|
albedo: Color::rgb(0.5, 0.4, 0.3),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// add entities to the world
|
||||||
|
command_buffer
|
||||||
|
.build()
|
||||||
|
// monkey
|
||||||
|
.add_entity(MeshEntity {
|
||||||
|
mesh: monkey_handle,
|
||||||
|
material: material_handle,
|
||||||
|
translation: Translation::new(-3.0, 0.0, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
// cube
|
||||||
|
.add_entity(MeshEntity {
|
||||||
|
mesh: cube_handle,
|
||||||
|
material: material_handle,
|
||||||
|
translation: Translation::new(0.0, 0.0, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
// sphere
|
||||||
|
.add_entity(MeshEntity {
|
||||||
|
mesh: sphere_handle,
|
||||||
|
material: material_handle,
|
||||||
|
translation: Translation::new(3.0, 0.0, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
// light
|
||||||
|
.add_entity(LightEntity {
|
||||||
|
translation: Translation::new(4.0, -4.0, 5.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
// camera
|
||||||
|
.add_entity(CameraEntity {
|
||||||
|
local_to_world: LocalToWorld(Mat4::look_at_rh(
|
||||||
|
Vec3::new(0.0, -10.0, 3.0),
|
||||||
|
Vec3::new(0.0, 0.0, 0.0),
|
||||||
|
Vec3::new(0.0, 0.0, 1.0),
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
60
examples/asset/hot_asset_reload.rs
Normal file
60
examples/asset/hot_asset_reload.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_asset::AssetServer;
|
||||||
|
|
||||||
|
/// Hot reloading allows you to modify assets on disk and they will be "live reloaded" while your game is running.
|
||||||
|
/// This lets you immediately see the results of your changes without restarting the game.
|
||||||
|
fn main() {
|
||||||
|
App::build()
|
||||||
|
.add_default_plugins()
|
||||||
|
.add_startup_system(setup.system())
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
command_buffer: &mut CommandBuffer,
|
||||||
|
mut asset_server: ResMut<AssetServer>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// load an asset folder
|
||||||
|
asset_server.load_asset_folder("assets").unwrap();
|
||||||
|
|
||||||
|
// tell the asset server to watch for changes
|
||||||
|
asset_server.watch_for_changes().unwrap();
|
||||||
|
|
||||||
|
// load the mesh
|
||||||
|
let mesh_handle = asset_server
|
||||||
|
.get_handle("assets/models/monkey/Monkey.gltf")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// now any changes to the mesh will be reloaded automatically!
|
||||||
|
|
||||||
|
// create a material for the mesh
|
||||||
|
let material_handle = materials.add(StandardMaterial {
|
||||||
|
albedo: Color::rgb(0.5, 0.4, 0.3),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// add entities to the world
|
||||||
|
command_buffer
|
||||||
|
.build()
|
||||||
|
// mesh
|
||||||
|
.add_entity(MeshEntity {
|
||||||
|
mesh: mesh_handle,
|
||||||
|
material: material_handle,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
// light
|
||||||
|
.add_entity(LightEntity {
|
||||||
|
translation: Translation::new(4.0, -4.0, 5.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
// camera
|
||||||
|
.add_entity(CameraEntity {
|
||||||
|
local_to_world: LocalToWorld(Mat4::look_at_rh(
|
||||||
|
Vec3::new(2.0, -6.0, 2.0),
|
||||||
|
Vec3::new(0.0, 0.0, 0.0),
|
||||||
|
Vec3::new(0.0, 0.0, 1.0),
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
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");
|
|
||||||
}
|
|
Loading…
Reference in a new issue