Asset system rework and GLTF scene loading (#693)

This commit is contained in:
Carter Anderson 2020-10-18 13:48:15 -07:00 committed by GitHub
parent a602f50c2c
commit c32e637384
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
150 changed files with 3681 additions and 2050 deletions

View file

@ -111,8 +111,8 @@ name = "texture_atlas"
path = "examples/2d/texture_atlas.rs"
[[example]]
name = "load_model"
path = "examples/3d/load_model.rs"
name = "load_gltf"
path = "examples/3d/load_gltf.rs"
[[example]]
name = "msaa"
@ -171,8 +171,8 @@ name = "asset_loading"
path = "examples/asset/asset_loading.rs"
[[example]]
name = "custom_loader"
path = "examples/asset/custom_asset_loading.rs"
name = "custom_asset"
path = "examples/asset/custom_asset.rs"
[[example]]
name = "audio"

3
assets/data/asset.custom Normal file
View file

@ -0,0 +1,3 @@
CustomAsset (
value: 42
)

View file

@ -1,3 +0,0 @@
MyCustomData (
num: 42
)

View file

@ -1,3 +0,0 @@
MySecondCustomData (
is_set: true
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View file

@ -0,0 +1,705 @@
{
"asset": {
"version": "2.0",
"generator": "babylon.js glTF exporter for Maya 2018 v20200228.3 (with minor hand modifications)"
},
"scene": 0,
"scenes": [
{
"nodes": [
0,
1,
2,
3,
4,
5
]
}
],
"nodes": [
{
"mesh": 0,
"name": "Hose_low"
},
{
"mesh": 1,
"name": "RubberWood_low"
},
{
"mesh": 2,
"name": "GlassPlastic_low"
},
{
"mesh": 3,
"name": "MetalParts_low"
},
{
"mesh": 4,
"name": "LeatherParts_low"
},
{
"mesh": 5,
"name": "Lenses_low"
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 1,
"TANGENT": 2,
"NORMAL": 3,
"TEXCOORD_0": 4
},
"indices": 0,
"material": 0
}
],
"name": "Hose_low"
},
{
"primitives": [
{
"attributes": {
"POSITION": 6,
"TANGENT": 7,
"NORMAL": 8,
"TEXCOORD_0": 9
},
"indices": 5,
"material": 1
}
],
"name": "RubberWood_low"
},
{
"primitives": [
{
"attributes": {
"POSITION": 11,
"TANGENT": 12,
"NORMAL": 13,
"TEXCOORD_0": 14
},
"indices": 10,
"material": 2
}
],
"name": "GlassPlastic_low"
},
{
"primitives": [
{
"attributes": {
"POSITION": 16,
"TANGENT": 17,
"NORMAL": 18,
"TEXCOORD_0": 19
},
"indices": 15,
"material": 3
}
],
"name": "MetalParts_low"
},
{
"primitives": [
{
"attributes": {
"POSITION": 21,
"TANGENT": 22,
"NORMAL": 23,
"TEXCOORD_0": 24
},
"indices": 20,
"material": 4
}
],
"name": "LeatherParts_low"
},
{
"primitives": [
{
"attributes": {
"POSITION": 26,
"TANGENT": 27,
"NORMAL": 28,
"TEXCOORD_0": 29
},
"indices": 25,
"material": 5
}
],
"name": "Lenses_low"
}
],
"accessors": [
{
"bufferView": 0,
"componentType": 5123,
"count": 59040,
"type": "SCALAR",
"name": "accessorIndices"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 10472,
"max": [
0.10810829,
0.356580257,
0.190409869
],
"min": [
-0.07221258,
0.104120225,
-0.088394776
],
"type": "VEC3",
"name": "accessorPositions"
},
{
"bufferView": 2,
"componentType": 5126,
"count": 10472,
"type": "VEC4",
"name": "accessorTangents"
},
{
"bufferView": 1,
"byteOffset": 125664,
"componentType": 5126,
"count": 10472,
"type": "VEC3",
"name": "accessorNormals"
},
{
"bufferView": 3,
"componentType": 5126,
"count": 10472,
"type": "VEC2",
"name": "accessorUVs"
},
{
"bufferView": 0,
"byteOffset": 118080,
"componentType": 5123,
"count": 72534,
"type": "SCALAR",
"name": "accessorIndices"
},
{
"bufferView": 1,
"byteOffset": 251328,
"componentType": 5126,
"count": 13638,
"max": [
0.162940636,
0.7025226,
0.200029165
],
"min": [
-0.158857465,
-2.14242937E-05,
-0.171545789
],
"type": "VEC3",
"name": "accessorPositions"
},
{
"bufferView": 2,
"byteOffset": 167552,
"componentType": 5126,
"count": 13638,
"type": "VEC4",
"name": "accessorTangents"
},
{
"bufferView": 1,
"byteOffset": 414984,
"componentType": 5126,
"count": 13638,
"type": "VEC3",
"name": "accessorNormals"
},
{
"bufferView": 3,
"byteOffset": 83776,
"componentType": 5126,
"count": 13638,
"type": "VEC2",
"name": "accessorUVs"
},
{
"bufferView": 0,
"byteOffset": 263148,
"componentType": 5123,
"count": 24408,
"type": "SCALAR",
"name": "accessorIndices"
},
{
"bufferView": 1,
"byteOffset": 578640,
"componentType": 5126,
"count": 4676,
"max": [
0.140494063,
0.61828655,
0.147373646
],
"min": [
-0.140846014,
0.440957,
-0.107818365
],
"type": "VEC3",
"name": "accessorPositions"
},
{
"bufferView": 2,
"byteOffset": 385760,
"componentType": 5126,
"count": 4676,
"type": "VEC4",
"name": "accessorTangents"
},
{
"bufferView": 1,
"byteOffset": 634752,
"componentType": 5126,
"count": 4676,
"type": "VEC3",
"name": "accessorNormals"
},
{
"bufferView": 3,
"byteOffset": 192880,
"componentType": 5126,
"count": 4676,
"type": "VEC2",
"name": "accessorUVs"
},
{
"bufferView": 0,
"byteOffset": 311964,
"componentType": 5123,
"count": 60288,
"type": "SCALAR",
"name": "accessorIndices"
},
{
"bufferView": 1,
"byteOffset": 690864,
"componentType": 5126,
"count": 13636,
"max": [
0.132708371,
0.6024364,
0.199477077
],
"min": [
-0.203642711,
0.02116075,
-0.147512689
],
"type": "VEC3",
"name": "accessorPositions"
},
{
"bufferView": 2,
"byteOffset": 460576,
"componentType": 5126,
"count": 13636,
"type": "VEC4",
"name": "accessorTangents"
},
{
"bufferView": 1,
"byteOffset": 854496,
"componentType": 5126,
"count": 13636,
"type": "VEC3",
"name": "accessorNormals"
},
{
"bufferView": 3,
"byteOffset": 230288,
"componentType": 5126,
"count": 13636,
"type": "VEC2",
"name": "accessorUVs"
},
{
"bufferView": 0,
"byteOffset": 432540,
"componentType": 5123,
"count": 65688,
"type": "SCALAR",
"name": "accessorIndices"
},
{
"bufferView": 1,
"byteOffset": 1018128,
"componentType": 5126,
"count": 12534,
"max": [
0.124933377,
0.716000438,
0.129168555
],
"min": [
-0.125863016,
0.2958266,
-0.1541516
],
"type": "VEC3",
"name": "accessorPositions"
},
{
"bufferView": 2,
"byteOffset": 678752,
"componentType": 5126,
"count": 12534,
"type": "VEC4",
"name": "accessorTangents"
},
{
"bufferView": 1,
"byteOffset": 1168536,
"componentType": 5126,
"count": 12534,
"type": "VEC3",
"name": "accessorNormals"
},
{
"bufferView": 3,
"byteOffset": 339376,
"componentType": 5126,
"count": 12534,
"type": "VEC2",
"name": "accessorUVs"
},
{
"bufferView": 0,
"byteOffset": 563916,
"componentType": 5123,
"count": 2208,
"type": "SCALAR",
"name": "accessorIndices"
},
{
"bufferView": 1,
"byteOffset": 1318944,
"componentType": 5126,
"count": 436,
"max": [
0.101920746,
0.5936986,
0.152926728
],
"min": [
-0.101920947,
0.5300429,
0.090174824
],
"type": "VEC3",
"name": "accessorPositions"
},
{
"bufferView": 2,
"byteOffset": 879296,
"componentType": 5126,
"count": 436,
"type": "VEC4",
"name": "accessorTangents"
},
{
"bufferView": 1,
"byteOffset": 1324176,
"componentType": 5126,
"count": 436,
"type": "VEC3",
"name": "accessorNormals"
},
{
"bufferView": 3,
"byteOffset": 439648,
"componentType": 5126,
"count": 436,
"type": "VEC2",
"name": "accessorUVs"
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 568332,
"name": "bufferViewScalar"
},
{
"buffer": 0,
"byteOffset": 568332,
"byteLength": 1329408,
"byteStride": 12,
"name": "bufferViewFloatVec3"
},
{
"buffer": 0,
"byteOffset": 1897740,
"byteLength": 886272,
"byteStride": 16,
"name": "bufferViewFloatVec4"
},
{
"buffer": 0,
"byteOffset": 2784012,
"byteLength": 443136,
"byteStride": 8,
"name": "bufferViewFloatVec2"
}
],
"buffers": [
{
"uri": "FlightHelmet.bin",
"byteLength": 3227148
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 2
},
"metallicRoughnessTexture": {
"index": 1
}
},
"normalTexture": {
"index": 0
},
"occlusionTexture": {
"index": 1
},
"doubleSided": true,
"name": "HoseMat"
},
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 2
},
"metallicRoughnessTexture": {
"index": 1
}
},
"normalTexture": {
"index": 0
},
"occlusionTexture": {
"index": 1
},
"name": "RubberWoodMat"
},
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 5
},
"metallicRoughnessTexture": {
"index": 4
}
},
"normalTexture": {
"index": 3
},
"occlusionTexture": {
"index": 4
},
"name": "GlassPlasticMat"
},
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 8
},
"metallicRoughnessTexture": {
"index": 7
}
},
"normalTexture": {
"index": 6
},
"occlusionTexture": {
"index": 7
},
"name": "MetalPartsMat"
},
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 11
},
"metallicRoughnessTexture": {
"index": 10
}
},
"normalTexture": {
"index": 9
},
"occlusionTexture": {
"index": 10
},
"name": "LeatherPartsMat"
},
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 14
},
"metallicRoughnessTexture": {
"index": 13
}
},
"normalTexture": {
"index": 12
},
"occlusionTexture": {
"index": 13
},
"alphaMode": "BLEND",
"name": "LensesMat"
}
],
"textures": [
{
"sampler": 0,
"source": 0,
"name": "FlightHelmet_Materials_RubberWoodMat_Normal.png"
},
{
"sampler": 0,
"source": 1,
"name": "FlightHelmet_Materials_RubberWoodMat_OcclusionRoughMetal.png"
},
{
"sampler": 0,
"source": 2,
"name": "FlightHelmet_Materials_RubberWoodMat_BaseColor.png"
},
{
"sampler": 0,
"source": 3,
"name": "FlightHelmet_Materials_GlassPlasticMat_Normal.png"
},
{
"sampler": 0,
"source": 4,
"name": "FlightHelmet_Materials_GlassPlasticMat_OcclusionRoughMetal.png"
},
{
"sampler": 0,
"source": 5,
"name": "FlightHelmet_Materials_GlassPlasticMat_BaseColor.png"
},
{
"sampler": 0,
"source": 6,
"name": "FlightHelmet_Materials_MetalPartsMat_Normal.png"
},
{
"sampler": 0,
"source": 7,
"name": "FlightHelmet_Materials_MetalPartsMat_OcclusionRoughMetal.png"
},
{
"sampler": 0,
"source": 8,
"name": "FlightHelmet_Materials_MetalPartsMat_BaseColor.png"
},
{
"sampler": 0,
"source": 9,
"name": "FlightHelmet_Materials_LeatherPartsMat_Normal.png"
},
{
"sampler": 0,
"source": 10,
"name": "FlightHelmet_Materials_LeatherPartsMat_OcclusionRoughMetal.png"
},
{
"sampler": 0,
"source": 11,
"name": "FlightHelmet_Materials_LeatherPartsMat_BaseColor.png"
},
{
"sampler": 0,
"source": 12,
"name": "FlightHelmet_Materials_LensesMat_Normal.png"
},
{
"sampler": 0,
"source": 13,
"name": "FlightHelmet_Materials_LensesMat_OcclusionRoughMetal.png"
},
{
"sampler": 0,
"source": 14,
"name": "FlightHelmet_Materials_LensesMat_BaseColor.png"
}
],
"images": [
{
"uri": "FlightHelmet_Materials_RubberWoodMat_Normal.png"
},
{
"uri": "FlightHelmet_Materials_RubberWoodMat_OcclusionRoughMetal.png"
},
{
"uri": "FlightHelmet_Materials_RubberWoodMat_BaseColor.png"
},
{
"uri": "FlightHelmet_Materials_GlassPlasticMat_Normal.png"
},
{
"uri": "FlightHelmet_Materials_GlassPlasticMat_OcclusionRoughMetal.png"
},
{
"uri": "FlightHelmet_Materials_GlassPlasticMat_BaseColor.png"
},
{
"uri": "FlightHelmet_Materials_MetalPartsMat_Normal.png"
},
{
"uri": "FlightHelmet_Materials_MetalPartsMat_OcclusionRoughMetal.png"
},
{
"uri": "FlightHelmet_Materials_MetalPartsMat_BaseColor.png"
},
{
"uri": "FlightHelmet_Materials_LeatherPartsMat_Normal.png"
},
{
"uri": "FlightHelmet_Materials_LeatherPartsMat_OcclusionRoughMetal.png"
},
{
"uri": "FlightHelmet_Materials_LeatherPartsMat_BaseColor.png"
},
{
"uri": "FlightHelmet_Materials_LensesMat_Normal.png"
},
{
"uri": "FlightHelmet_Materials_LensesMat_OcclusionRoughMetal.png"
},
{
"uri": "FlightHelmet_Materials_LensesMat_BaseColor.png"
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9987
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -170,7 +170,8 @@ impl AppBuilder {
}
pub fn add_default_stages(&mut self) -> &mut Self {
self.add_startup_stage(startup_stage::STARTUP)
self.add_startup_stage(startup_stage::PRE_STARTUP)
.add_startup_stage(startup_stage::STARTUP)
.add_startup_stage(startup_stage::POST_STARTUP)
.add_stage(stage::FIRST)
.add_stage(stage::EVENT_UPDATE)

View file

@ -1,5 +1,8 @@
/// Name of app stage that runs once before the startup stage
pub const PRE_STARTUP: &str = "pre_startup";
/// Name of app stage that runs once when an app starts up
pub const STARTUP: &str = "startup";
/// Name of app stage that runs once after startup
/// Name of app stage that runs once after the startup stage
pub const POST_STARTUP: &str = "post_startup";

View file

@ -28,16 +28,12 @@ bevy_utils = { path = "../bevy_utils", version = "0.2.1" }
# other
uuid = { version = "0.8", features = ["v4", "serde"] }
serde = { version = "1", features = ["derive"] }
ron = "0.6.2"
crossbeam-channel = "0.4.4"
anyhow = "1.0"
thiserror = "1.0"
downcast-rs = "1.2.0"
log = { version = "0.4", features = ["release_max_level_info"] }
notify = { version = "5.0.0-pre.2", optional = true }
parking_lot = "0.11.0"
async-trait = "0.1.40"
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" }
web-sys = { version = "0.3", features = ["Request", "Window", "Response"]}
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
rand = "0.7.3"

View file

@ -1,414 +1,438 @@
use crate::{
filesystem_watcher::FilesystemWatcher, AssetLoadError, AssetLoadRequestHandler, AssetLoader,
Assets, Handle, HandleId, LoadRequest,
path::{AssetPath, AssetPathId, SourcePathId},
Asset, AssetIo, AssetIoError, AssetLifecycle, AssetLifecycleChannel, AssetLifecycleEvent,
AssetLoader, Assets, FileAssetIo, Handle, HandleId, HandleUntyped, LabelId, LoadContext,
LoadState, RefChange, RefChangeChannel, SourceInfo, SourceMeta,
};
use anyhow::Result;
use bevy_ecs::{Res, Resource, Resources};
use bevy_ecs::Res;
use bevy_tasks::TaskPool;
use bevy_utils::{HashMap, HashSet};
use bevy_utils::HashMap;
use crossbeam_channel::TryRecvError;
use parking_lot::RwLock;
use std::{
fs, io,
path::{Path, PathBuf},
sync::Arc,
};
use std::{collections::hash_map::Entry, path::Path, sync::Arc};
use thiserror::Error;
/// The type used for asset versioning
pub type AssetVersion = usize;
use uuid::Uuid;
/// Errors that occur while loading assets with an AssetServer
#[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,
MissingAssetLoader(Option<String>),
#[error("The given type does not match the type of the loaded asset.")]
IncorrectHandleType,
#[error("Encountered an error while loading an asset.")]
AssetLoadError(#[from] AssetLoadError),
#[error("Encountered an io error.")]
Io(#[from] io::Error),
#[error("Failed to watch asset folder.")]
AssetWatchError { path: PathBuf },
AssetLoaderError(anyhow::Error),
#[error("PathLoader encountered an error")]
PathLoaderError(#[from] AssetIoError),
}
/// Info about a specific asset, such as its path and its current load state
#[derive(Clone, Debug)]
pub struct AssetInfo {
pub handle_id: HandleId,
pub path: PathBuf,
pub load_state: LoadState,
#[derive(Default)]
pub(crate) struct AssetRefCounter {
pub(crate) channel: Arc<RefChangeChannel>,
pub(crate) ref_counts: Arc<RwLock<HashMap<HandleId, usize>>>,
}
/// The load state of an asset
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LoadState {
Loading(AssetVersion),
Loaded(AssetVersion),
Failed(AssetVersion),
}
impl LoadState {
pub fn get_version(&self) -> AssetVersion {
match *self {
LoadState::Loaded(version) => version,
LoadState::Loading(version) => version,
LoadState::Failed(version) => version,
}
}
pub struct AssetServerInternal<TAssetIo: AssetIo = FileAssetIo> {
pub(crate) asset_io: TAssetIo,
pub(crate) asset_ref_counter: AssetRefCounter,
pub(crate) asset_sources: Arc<RwLock<HashMap<SourcePathId, SourceInfo>>>,
pub(crate) asset_lifecycles: Arc<RwLock<HashMap<Uuid, Box<dyn AssetLifecycle>>>>,
loaders: RwLock<Vec<Arc<Box<dyn AssetLoader>>>>,
extension_to_loader_index: RwLock<HashMap<String, usize>>,
handle_to_path: Arc<RwLock<HashMap<HandleId, AssetPath<'static>>>>,
task_pool: TaskPool,
}
/// Loads assets from the filesystem on background threads
pub struct AssetServer {
asset_folders: RwLock<Vec<PathBuf>>,
asset_handlers: RwLock<Vec<Arc<dyn AssetLoadRequestHandler>>>,
loaders: Vec<Resources>,
task_pool: TaskPool,
extension_to_handler_index: HashMap<String, usize>,
extension_to_loader_index: HashMap<String, usize>,
asset_info: RwLock<HashMap<HandleId, AssetInfo>>,
asset_info_paths: RwLock<HashMap<PathBuf, HandleId>>,
#[cfg(feature = "filesystem_watcher")]
filesystem_watcher: Arc<RwLock<Option<FilesystemWatcher>>>,
pub struct AssetServer<TAssetIo: AssetIo = FileAssetIo> {
pub(crate) server: Arc<AssetServerInternal<TAssetIo>>,
}
impl AssetServer {
pub fn new(task_pool: TaskPool) -> Self {
impl<TAssetIo: AssetIo> Clone for AssetServer<TAssetIo> {
fn clone(&self) -> Self {
Self {
server: self.server.clone(),
}
}
}
impl<TAssetIo: AssetIo> AssetServer<TAssetIo> {
pub fn new(source_io: TAssetIo, task_pool: TaskPool) -> Self {
AssetServer {
asset_folders: Default::default(),
asset_handlers: Default::default(),
loaders: Default::default(),
extension_to_handler_index: Default::default(),
extension_to_loader_index: Default::default(),
asset_info_paths: Default::default(),
asset_info: Default::default(),
task_pool,
#[cfg(feature = "filesystem_watcher")]
filesystem_watcher: Arc::new(RwLock::new(None)),
server: Arc::new(AssetServerInternal {
loaders: Default::default(),
extension_to_loader_index: Default::default(),
asset_sources: Default::default(),
asset_ref_counter: Default::default(),
handle_to_path: Default::default(),
asset_lifecycles: Default::default(),
task_pool,
asset_io: source_io,
}),
}
}
pub fn add_handler<T>(&mut self, asset_handler: T)
where
T: AssetLoadRequestHandler,
{
let mut asset_handlers = self.asset_handlers.write();
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(Arc::new(asset_handler));
pub(crate) fn register_asset_type<T: Asset>(&self) -> Assets<T> {
self.server.asset_lifecycles.write().insert(
T::TYPE_UUID,
Box::new(AssetLifecycleChannel::<T>::default()),
);
Assets::new(self.server.asset_ref_counter.channel.sender.clone())
}
pub fn add_loader<TLoader, TAsset>(&mut self, loader: TLoader)
pub fn add_loader<T>(&self, loader: T)
where
TLoader: AssetLoader<TAsset>,
TAsset: 'static,
T: AssetLoader,
{
let loader_index = self.loaders.len();
let mut loaders = self.server.loaders.write();
let loader_index = loaders.len();
for extension in loader.extensions().iter() {
self.extension_to_loader_index
self.server
.extension_to_loader_index
.write()
.insert(extension.to_string(), loader_index);
}
let mut resources = Resources::default();
resources.insert::<Box<dyn AssetLoader<TAsset>>>(Box::new(loader));
self.loaders.push(resources);
loaders.push(Arc::new(Box::new(loader)));
}
pub fn load_asset_folder<P: AsRef<Path>>(
&self,
path: P,
) -> Result<Vec<HandleId>, AssetServerError> {
let root_path = self.get_root_path()?;
let asset_folder = root_path.join(path);
let handle_ids = self.load_assets_in_folder_recursive(&asset_folder)?;
self.asset_folders.write().push(asset_folder);
Ok(handle_ids)
}
pub fn get_handle<T, P: AsRef<Path>>(&self, path: P) -> Option<Handle<T>> {
self.asset_info_paths
.read()
.get(path.as_ref())
.map(|handle_id| Handle::from(*handle_id))
}
#[cfg(feature = "filesystem_watcher")]
fn watch_path_for_changes<P: AsRef<Path>>(
filesystem_watcher: &mut Option<FilesystemWatcher>,
path: P,
) -> Result<(), AssetServerError> {
if let Some(watcher) = filesystem_watcher {
watcher
.watch(&path)
.map_err(|_error| AssetServerError::AssetWatchError {
path: path.as_ref().to_owned(),
})?;
}
Ok(())
}
#[cfg(feature = "filesystem_watcher")]
pub fn watch_for_changes(&self) -> Result<(), AssetServerError> {
let mut filesystem_watcher = self.filesystem_watcher.write();
let _ = filesystem_watcher.get_or_insert_with(FilesystemWatcher::default);
// watch current files
let asset_info_paths = self.asset_info_paths.read();
for asset_path in asset_info_paths.keys() {
Self::watch_path_for_changes(&mut filesystem_watcher, asset_path)?;
}
self.server.asset_io.watch_for_changes()?;
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> {
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
Ok(PathBuf::from(manifest_dir))
} else {
match std::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 get_handle<T: Asset, I: Into<HandleId>>(&self, id: I) -> Handle<T> {
let sender = self.server.asset_ref_counter.channel.sender.clone();
Handle::strong(id.into(), sender)
}
#[cfg(target_arch = "wasm32")]
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> {
Ok(PathBuf::from("/"))
pub fn get_handle_untyped<I: Into<HandleId>>(&self, id: I) -> HandleUntyped {
let sender = self.server.asset_ref_counter.channel.sender.clone();
HandleUntyped::strong(id.into(), sender)
}
// TODO: add type checking here. people shouldn't be able to request a Handle<Texture> for a Mesh asset
pub fn load<T, P: AsRef<Path>>(&self, path: P) -> Result<Handle<T>, AssetServerError> {
self.load_untyped(self.get_root_path()?.join(path))
.map(Handle::from)
}
pub fn load_sync<T: Resource, P: AsRef<Path>>(
fn get_asset_loader(
&self,
assets: &mut Assets<T>,
path: P,
) -> Result<Handle<T>, AssetServerError>
where
T: 'static,
{
let path = self.get_root_path()?.join(path);
if let Some(ref extension) = path.extension() {
if let Some(index) = self.extension_to_loader_index.get(
extension
.to_str()
.expect("extension should be a valid string"),
) {
let mut asset_info_paths = self.asset_info_paths.write();
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(path.as_ref())?;
let handle = Handle::from(handle_id);
assets.set(handle, asset);
asset_info_paths.insert(path.to_owned(), handle_id);
Ok(handle)
} else {
Err(AssetServerError::MissingAssetHandler)
}
} else {
Err(AssetServerError::MissingAssetHandler)
}
}
pub fn load_untyped<P: AsRef<Path>>(&self, path: P) -> Result<HandleId, AssetServerError> {
let path = path.as_ref();
if let Some(ref extension) = path.extension() {
if let Some(index) = self.extension_to_handler_index.get(
extension
.to_str()
.expect("Extension should be a valid string."),
) {
let mut new_version = 0;
let handle_id = {
let mut asset_info = self.asset_info.write();
let mut asset_info_paths = self.asset_info_paths.write();
if let Some(asset_info) = asset_info_paths
.get(path)
.and_then(|handle_id| asset_info.get_mut(&handle_id))
{
asset_info.load_state =
if let LoadState::Loaded(_version) = asset_info.load_state {
new_version += 1;
LoadState::Loading(new_version)
} else {
LoadState::Loading(new_version)
};
asset_info.handle_id
} else {
let handle_id = HandleId::new();
asset_info.insert(
handle_id,
AssetInfo {
handle_id,
path: path.to_owned(),
load_state: LoadState::Loading(new_version),
},
);
asset_info_paths.insert(path.to_owned(), handle_id);
handle_id
}
};
let load_request = LoadRequest {
handle_id,
path: path.to_owned(),
handler_index: *index,
version: new_version,
};
let handlers = self.asset_handlers.read();
let request_handler = handlers[load_request.handler_index].clone();
self.task_pool
.spawn(async move {
request_handler.handle_request(&load_request).await;
})
.detach();
// TODO: watching each asset explicitly is a simpler implementation, its possible it would be more efficient to watch
// folders instead (when possible)
#[cfg(feature = "filesystem_watcher")]
Self::watch_path_for_changes(
&mut self.filesystem_watcher.write(),
path.to_owned(),
)?;
Ok(handle_id)
} else {
Err(AssetServerError::MissingAssetHandler)
}
} else {
Err(AssetServerError::MissingAssetHandler)
}
}
pub fn set_load_state(&self, handle_id: HandleId, load_state: LoadState) {
if let Some(asset_info) = self.asset_info.write().get_mut(&handle_id) {
if load_state.get_version() >= asset_info.load_state.get_version() {
asset_info.load_state = load_state;
}
}
}
pub fn get_load_state_untyped(&self, handle_id: HandleId) -> Option<LoadState> {
self.asset_info
extension: &str,
) -> Result<Arc<Box<dyn AssetLoader>>, AssetServerError> {
self.server
.extension_to_loader_index
.read()
.get(&handle_id)
.map(|asset_info| asset_info.load_state.clone())
.get(extension)
.map(|index| self.server.loaders.read()[*index].clone())
.ok_or_else(|| AssetServerError::MissingAssetLoader(Some(extension.to_string())))
}
pub fn get_load_state<T>(&self, handle: Handle<T>) -> Option<LoadState> {
self.get_load_state_untyped(handle.id)
fn get_path_asset_loader<P: AsRef<Path>>(
&self,
path: P,
) -> Result<Arc<Box<dyn AssetLoader>>, AssetServerError> {
path.as_ref()
.extension()
.and_then(|e| e.to_str())
.ok_or(AssetServerError::MissingAssetLoader(None))
.and_then(|extension| self.get_asset_loader(extension))
}
pub fn get_group_load_state(&self, handle_ids: &[HandleId]) -> Option<LoadState> {
let mut load_state = LoadState::Loaded(0);
for handle_id in handle_ids.iter() {
match self.get_load_state_untyped(*handle_id) {
Some(LoadState::Loaded(_)) => continue,
Some(LoadState::Loading(_)) => {
load_state = LoadState::Loading(0);
}
Some(LoadState::Failed(_)) => return Some(LoadState::Failed(0)),
None => return None,
pub fn get_handle_path<H: Into<HandleId>>(&self, handle: H) -> Option<AssetPath<'_>> {
self.server
.handle_to_path
.read()
.get(&handle.into())
.cloned()
}
pub fn get_load_state<H: Into<HandleId>>(&self, handle: H) -> LoadState {
match handle.into() {
HandleId::AssetPathId(id) => {
let asset_sources = self.server.asset_sources.read();
asset_sources
.get(&id.source_path_id())
.map_or(LoadState::NotLoaded, |info| info.load_state)
}
HandleId::Id(_, _) => LoadState::NotLoaded,
}
}
pub fn get_group_load_state(&self, handles: impl IntoIterator<Item = HandleId>) -> LoadState {
let mut load_state = LoadState::Loaded;
for handle_id in handles {
match handle_id {
HandleId::AssetPathId(id) => match self.get_load_state(id) {
LoadState::Loaded => continue,
LoadState::Loading => {
load_state = LoadState::Loading;
}
LoadState::Failed => return LoadState::Failed,
LoadState::NotLoaded => return LoadState::NotLoaded,
},
HandleId::Id(_, _) => return LoadState::NotLoaded,
}
}
Some(load_state)
load_state
}
fn load_assets_in_folder_recursive(
pub fn load<'a, T: Asset, P: Into<AssetPath<'a>>>(&self, path: P) -> Handle<T> {
self.load_untyped(path).typed()
}
// TODO: properly set failed LoadState in all failure cases
fn load_sync<'a, P: Into<AssetPath<'a>>>(
&self,
path: &Path,
) -> Result<Vec<HandleId>, AssetServerError> {
if !path.is_dir() {
path: P,
force: bool,
) -> Result<AssetPathId, AssetServerError> {
let asset_path: AssetPath = path.into();
let asset_loader = self.get_path_asset_loader(asset_path.path())?;
let asset_path_id: AssetPathId = asset_path.get_id();
// load metadata and update source info. this is done in a scope to ensure we release the locks before loading
let version = {
let mut asset_sources = self.server.asset_sources.write();
let source_info = match asset_sources.entry(asset_path_id.source_path_id()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(SourceInfo {
asset_types: Default::default(),
committed_assets: Default::default(),
load_state: LoadState::NotLoaded,
meta: None,
path: asset_path.path().to_owned(),
version: 0,
}),
};
// if asset is already loaded (or is loading), don't load again
if !force
&& source_info
.committed_assets
.contains(&asset_path_id.label_id())
{
return Ok(asset_path_id);
}
source_info.load_state = LoadState::Loading;
source_info.committed_assets.clear();
source_info.version += 1;
source_info.meta = None;
source_info.version
};
// load the asset bytes
let bytes = self.server.asset_io.load_path(asset_path.path())?;
// load the asset source using the corresponding AssetLoader
let mut load_context = LoadContext::new(
asset_path.path(),
&self.server.asset_ref_counter.channel,
&self.server.asset_io,
version,
);
asset_loader
.load(&bytes, &mut load_context)
.map_err(AssetServerError::AssetLoaderError)?;
// if version has changed since we loaded and grabbed a lock, return. theres is a newer version being loaded
let mut asset_sources = self.server.asset_sources.write();
let source_info = asset_sources
.get_mut(&asset_path_id.source_path_id())
.expect("AssetSource should exist at this point");
if version != source_info.version {
return Ok(asset_path_id);
}
// if all assets have been committed already (aka there were 0), set state to "Loaded"
if source_info.is_loaded() {
source_info.load_state = LoadState::Loaded;
}
// reset relevant SourceInfo fields
source_info.committed_assets.clear();
// TODO: queue free old assets
source_info.asset_types.clear();
source_info.meta = Some(SourceMeta {
assets: load_context.get_asset_metas(),
});
// load asset dependencies and prepare asset type hashmap
for (label, loaded_asset) in load_context.labeled_assets.iter_mut() {
let label_id = LabelId::from(label.as_ref().map(|label| label.as_str()));
let type_uuid = loaded_asset.value.as_ref().unwrap().type_uuid();
source_info.asset_types.insert(label_id, type_uuid);
for dependency in loaded_asset.dependencies.iter() {
self.load_untyped(dependency.clone());
}
}
self.server
.asset_io
.watch_path_for_changes(asset_path.path())
.unwrap();
self.create_assets_in_load_context(&mut load_context);
Ok(asset_path_id)
}
pub fn load_untyped<'a, P: Into<AssetPath<'a>>>(&self, path: P) -> HandleUntyped {
let handle_id = self.load_untracked(path, false);
self.get_handle_untyped(handle_id)
}
pub(crate) fn load_untracked<'a, P: Into<AssetPath<'a>>>(
&self,
path: P,
force: bool,
) -> HandleId {
let asset_path: AssetPath<'a> = path.into();
let server = self.clone();
let owned_path = asset_path.to_owned();
self.server
.task_pool
.spawn(async move {
server.load_sync(owned_path, force).unwrap();
})
.detach();
asset_path.into()
}
pub fn load_folder<P: AsRef<Path>>(
&self,
path: P,
) -> Result<Vec<HandleUntyped>, AssetServerError> {
let path = path.as_ref();
if !self.server.asset_io.is_directory(path) {
return Err(AssetServerError::AssetFolderNotADirectory(
path.to_str().unwrap().to_string(),
));
}
let root_path = self.get_root_path()?;
let mut handle_ids = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
let child_path = entry.path();
if child_path.is_dir() {
handle_ids.extend(self.load_assets_in_folder_recursive(&child_path)?);
let mut handles = Vec::new();
for child_path in self.server.asset_io.read_directory(path.as_ref())? {
if self.server.asset_io.is_directory(&child_path) {
handles.extend(self.load_folder(&child_path)?);
} else {
let relative_child_path = child_path.strip_prefix(&root_path).unwrap();
let handle = match self.load_untyped(
relative_child_path
.to_str()
.expect("Path should be a valid string"),
) {
Ok(handle) => handle,
Err(AssetServerError::MissingAssetHandler) => continue,
Err(err) => return Err(err),
};
handle_ids.push(handle);
if self.get_path_asset_loader(&child_path).is_err() {
continue;
}
let handle =
self.load_untyped(child_path.to_str().expect("Path should be a valid string"));
handles.push(handle);
}
}
Ok(handle_ids)
Ok(handles)
}
}
#[cfg(feature = "filesystem_watcher")]
pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
let mut changed = HashSet::default();
loop {
let result = {
let rwlock_guard = asset_server.filesystem_watcher.read();
if let Some(filesystem_watcher) = rwlock_guard.as_ref() {
filesystem_watcher.receiver.try_recv()
} else {
break;
}
};
let event = match result {
Ok(result) => result.unwrap(),
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => panic!("FilesystemWatcher disconnected"),
};
if let notify::event::Event {
kind: notify::event::EventKind::Modify(_),
paths,
..
} = event
{
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(_) => {}
pub fn free_unused_assets(&self) {
let receiver = &self.server.asset_ref_counter.channel.receiver;
let mut ref_counts = self.server.asset_ref_counter.ref_counts.write();
let asset_sources = self.server.asset_sources.read();
let mut potential_frees = Vec::new();
loop {
let ref_change = match receiver.try_recv() {
Ok(ref_change) => ref_change,
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => panic!("RefChange channel disconnected"),
};
match ref_change {
RefChange::Increment(handle_id) => *ref_counts.entry(handle_id).or_insert(0) += 1,
RefChange::Decrement(handle_id) => {
let entry = ref_counts.entry(handle_id).or_insert(0);
*entry -= 1;
if *entry == 0 {
potential_frees.push(handle_id);
}
}
}
changed.extend(paths);
}
if !potential_frees.is_empty() {
let asset_lifecycles = self.server.asset_lifecycles.read();
for potential_free in potential_frees {
if let Some(i) = ref_counts.get(&potential_free).cloned() {
if i == 0 {
let type_uuid = match potential_free {
HandleId::Id(type_uuid, _) => Some(type_uuid),
HandleId::AssetPathId(id) => asset_sources
.get(&id.source_path_id())
.and_then(|source_info| source_info.get_asset_type(id.label_id())),
};
if let Some(type_uuid) = type_uuid {
if let Some(asset_lifecycle) = asset_lifecycles.get(&type_uuid) {
asset_lifecycle.free_asset(potential_free);
}
}
}
}
}
}
}
fn create_assets_in_load_context(&self, load_context: &mut LoadContext) {
let asset_lifecycles = self.server.asset_lifecycles.read();
for (label, asset) in load_context.labeled_assets.iter_mut() {
let asset_value = asset
.value
.take()
.expect("Asset should exist at this point");
if let Some(asset_lifecycle) = asset_lifecycles.get(&asset_value.type_uuid()) {
let asset_path =
AssetPath::new_ref(&load_context.path, label.as_ref().map(|l| l.as_str()));
asset_lifecycle.create_asset(asset_path.into(), asset_value, load_context.version);
} else {
panic!("Failed to find AssetLifecycle for label {:?}, which has an asset type {:?}. Are you sure that is a registered asset type?", label, asset_value.type_uuid());
}
}
}
pub(crate) fn update_asset_storage<T: Asset>(&self, assets: &mut Assets<T>) {
let asset_lifecycles = self.server.asset_lifecycles.read();
let asset_lifecycle = asset_lifecycles.get(&T::TYPE_UUID).unwrap();
let mut asset_sources = self.server.asset_sources.write();
let channel = asset_lifecycle
.downcast_ref::<AssetLifecycleChannel<T>>()
.unwrap();
loop {
match channel.receiver.try_recv() {
Ok(AssetLifecycleEvent::Create(result)) => {
// update SourceInfo if this asset was loaded from an AssetPath
if let HandleId::AssetPathId(id) = result.id {
if let Some(source_info) = asset_sources.get_mut(&id.source_path_id()) {
if source_info.version == result.version {
source_info.committed_assets.insert(id.label_id());
if source_info.is_loaded() {
source_info.load_state = LoadState::Loaded;
}
}
}
}
assets.set(result.id, result.asset);
}
Ok(AssetLifecycleEvent::Free(handle_id)) => {
if let HandleId::AssetPathId(id) = handle_id {
if let Some(source_info) = asset_sources.get_mut(&id.source_path_id()) {
source_info.committed_assets.remove(&id.label_id());
if source_info.is_loaded() {
source_info.load_state = LoadState::Loaded;
}
}
}
assets.remove(handle_id);
}
Err(TryRecvError::Empty) => {
break;
}
Err(TryRecvError::Disconnected) => panic!("AssetChannel disconnected"),
}
}
}
}
pub fn free_unused_assets_system(asset_server: Res<AssetServer>) {
asset_server.free_unused_assets();
}

View file

@ -1,92 +1,133 @@
use crate::{
update_asset_storage_system, AssetChannel, AssetLoader, AssetServer, ChannelAssetHandler,
Handle, HandleId,
update_asset_storage_system, Asset, AssetLoader, AssetServer, Handle, HandleId, RefChange,
};
use bevy_app::{prelude::Events, AppBuilder};
use bevy_ecs::{FromResources, IntoQuerySystem, ResMut, Resource};
use bevy_ecs::{FromResources, IntoQuerySystem, ResMut};
use bevy_type_registry::RegisterType;
use bevy_utils::HashMap;
use crossbeam_channel::Sender;
use std::fmt::Debug;
/// Events that happen on assets of type `T`
#[derive(Debug)]
pub enum AssetEvent<T: Resource> {
pub enum AssetEvent<T: Asset> {
Created { handle: Handle<T> },
Modified { handle: Handle<T> },
Removed { handle: Handle<T> },
}
/// Stores Assets of a given type and tracks changes to them.
#[derive(Debug)]
pub struct Assets<T: Resource> {
assets: HashMap<Handle<T>, T>,
events: Events<AssetEvent<T>>,
impl<T: Asset> Debug for AssetEvent<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AssetEvent::Created { handle } => f
.debug_struct(&format!(
"AssetEvent<{}>::Created",
std::any::type_name::<T>()
))
.field("handle", &handle.id)
.finish(),
AssetEvent::Modified { handle } => f
.debug_struct(&format!(
"AssetEvent<{}>::Modified",
std::any::type_name::<T>()
))
.field("handle", &handle.id)
.finish(),
AssetEvent::Removed { handle } => f
.debug_struct(&format!(
"AssetEvent<{}>::Removed",
std::any::type_name::<T>()
))
.field("handle", &handle.id)
.finish(),
}
}
}
impl<T: Resource> Default for Assets<T> {
fn default() -> Self {
/// Stores Assets of a given type and tracks changes to them.
#[derive(Debug)]
pub struct Assets<T: Asset> {
assets: HashMap<HandleId, T>,
events: Events<AssetEvent<T>>,
pub(crate) ref_change_sender: Sender<RefChange>,
}
impl<T: Asset> Assets<T> {
pub(crate) fn new(ref_change_sender: Sender<RefChange>) -> Self {
Assets {
assets: HashMap::default(),
events: Events::default(),
ref_change_sender,
}
}
}
impl<T: Resource> Assets<T> {
pub fn add(&mut self, asset: T) -> Handle<T> {
let handle = Handle::new();
self.assets.insert(handle, asset);
self.events.send(AssetEvent::Created { handle });
handle
let id = HandleId::random::<T>();
self.assets.insert(id, asset);
self.events.send(AssetEvent::Created {
handle: Handle::weak(id),
});
self.get_handle(id)
}
pub fn set(&mut self, handle: Handle<T>, asset: T) {
let exists = self.assets.contains_key(&handle);
self.assets.insert(handle, asset);
if exists {
self.events.send(AssetEvent::Modified { handle });
pub fn set<H: Into<HandleId>>(&mut self, handle: H, asset: T) -> Handle<T> {
let id: HandleId = handle.into();
if self.assets.insert(id, asset).is_some() {
self.events.send(AssetEvent::Modified {
handle: Handle::weak(id),
});
} else {
self.events.send(AssetEvent::Created { handle });
self.events.send(AssetEvent::Created {
handle: Handle::weak(id),
});
}
self.get_handle(id)
}
pub fn set_untracked<H: Into<HandleId>>(&mut self, handle: H, asset: T) {
let id: HandleId = handle.into();
if self.assets.insert(id, asset).is_some() {
self.events.send(AssetEvent::Modified {
handle: Handle::weak(id),
});
} else {
self.events.send(AssetEvent::Created {
handle: Handle::weak(id),
});
}
}
pub fn add_default(&mut self, asset: T) -> Handle<T> {
let handle = Handle::default();
let exists = self.assets.contains_key(&handle);
self.assets.insert(handle, asset);
if exists {
self.events.send(AssetEvent::Modified { handle });
} else {
self.events.send(AssetEvent::Created { handle });
}
handle
pub fn get<H: Into<HandleId>>(&self, handle: H) -> Option<&T> {
self.assets.get(&handle.into())
}
pub fn get_with_id(&self, id: HandleId) -> Option<&T> {
self.get(&Handle::from_id(id))
pub fn contains<H: Into<HandleId>>(&self, handle: H) -> bool {
self.assets.contains_key(&handle.into())
}
pub fn get_with_id_mut(&mut self, id: HandleId) -> Option<&mut T> {
self.get_mut(&Handle::from_id(id))
pub fn get_mut<H: Into<HandleId>>(&mut self, handle: H) -> Option<&mut T> {
let id: HandleId = handle.into();
self.events.send(AssetEvent::Modified {
handle: Handle::weak(id),
});
self.assets.get_mut(&id)
}
pub fn get(&self, handle: &Handle<T>) -> Option<&T> {
self.assets.get(&handle)
pub fn get_handle<H: Into<HandleId>>(&self, handle: H) -> Handle<T> {
Handle::strong(handle.into(), self.ref_change_sender.clone())
}
pub fn get_mut(&mut self, handle: &Handle<T>) -> Option<&mut T> {
self.events.send(AssetEvent::Modified { handle: *handle });
self.assets.get_mut(&handle)
}
pub fn get_or_insert_with(
pub fn get_or_insert_with<H: Into<HandleId>>(
&mut self,
handle: Handle<T>,
handle: H,
insert_fn: impl FnOnce() -> T,
) -> &mut T {
let mut event = None;
let borrowed = self.assets.entry(handle).or_insert_with(|| {
event = Some(AssetEvent::Created { handle });
let id: HandleId = handle.into();
let borrowed = self.assets.entry(id).or_insert_with(|| {
event = Some(AssetEvent::Created {
handle: Handle::weak(id),
});
insert_fn()
});
@ -96,12 +137,23 @@ impl<T: Resource> Assets<T> {
borrowed
}
pub fn iter(&self) -> impl Iterator<Item = (Handle<T>, &T)> {
pub fn iter(&self) -> impl Iterator<Item = (HandleId, &T)> {
self.assets.iter().map(|(k, v)| (*k, v))
}
pub fn remove(&mut self, handle: &Handle<T>) -> Option<T> {
self.assets.remove(&handle)
pub fn ids(&self) -> impl Iterator<Item = HandleId> + '_ {
self.assets.keys().cloned()
}
pub fn remove<H: Into<HandleId>>(&mut self, handle: H) -> Option<T> {
let id: HandleId = handle.into();
let asset = self.assets.remove(&id);
if asset.is_some() {
self.events.send(AssetEvent::Removed {
handle: Handle::weak(id),
});
}
asset
}
/// Clears the inner asset map, removing all key-value pairs.
@ -132,75 +184,67 @@ impl<T: Resource> Assets<T> {
) {
events.extend(assets.events.drain())
}
pub fn len(&self) -> usize {
self.assets.len()
}
pub fn is_empty(&self) -> bool {
self.assets.is_empty()
}
}
/// [AppBuilder] extension methods for adding new asset types
pub trait AddAsset {
fn add_asset<T>(&mut self) -> &mut Self
where
T: Send + Sync + 'static;
fn add_asset_loader<TAsset, TLoader>(&mut self) -> &mut Self
T: Asset;
fn init_asset_loader<T>(&mut self) -> &mut Self
where
TLoader: AssetLoader<TAsset> + FromResources,
TAsset: Send + Sync + 'static;
fn add_asset_loader_from_instance<TAsset, TLoader>(&mut self, instance: TLoader) -> &mut Self
T: AssetLoader + FromResources;
fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
where
TLoader: AssetLoader<TAsset> + FromResources,
TAsset: Send + Sync + 'static;
T: AssetLoader;
}
impl AddAsset for AppBuilder {
fn add_asset<T>(&mut self) -> &mut Self
where
T: Resource,
T: Asset,
{
self.init_resource::<Assets<T>>()
let assets = {
let asset_server = self.resources().get::<AssetServer>().unwrap();
asset_server.register_asset_type::<T>()
};
self.add_resource(assets)
.register_component::<Handle<T>>()
.add_system_to_stage(
super::stage::ASSET_EVENTS,
Assets::<T>::asset_event_system.system(),
)
.add_system_to_stage(
crate::stage::LOAD_ASSETS,
update_asset_storage_system::<T>.system(),
)
.add_event::<AssetEvent<T>>()
}
fn add_asset_loader_from_instance<TAsset, TLoader>(&mut self, instance: TLoader) -> &mut Self
fn init_asset_loader<T>(&mut self) -> &mut Self
where
TLoader: AssetLoader<TAsset> + FromResources,
TAsset: Send + Sync + 'static,
T: AssetLoader + FromResources,
{
{
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(instance);
let handler = ChannelAssetHandler::new(
TLoader::from_resources(self.resources()),
asset_channel.sender.clone(),
);
asset_server.add_handler(handler);
}
self
self.add_asset_loader(T::from_resources(self.resources()))
}
fn add_asset_loader<TAsset, TLoader>(&mut self) -> &mut Self
fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
where
TLoader: AssetLoader<TAsset> + FromResources,
TAsset: Send + Sync + 'static,
T: AssetLoader,
{
self.add_asset_loader_from_instance::<TAsset, TLoader>(TLoader::from_resources(
self.resources(),
))
self.resources()
.get_mut::<AssetServer>()
.expect("AssetServer does not exist. Consider adding it as a resource.")
.add_loader(loader);
self
}
}

View file

@ -2,27 +2,54 @@ use std::{
cmp::Ordering,
fmt::Debug,
hash::{Hash, Hasher},
marker::PhantomData,
};
use bevy_property::{Properties, Property};
use crossbeam_channel::{Receiver, Sender};
use serde::{Deserialize, Serialize};
use std::{any::TypeId, marker::PhantomData};
use uuid::Uuid;
/// The ID of the "default" asset
pub(crate) const DEFAULT_HANDLE_ID: HandleId =
HandleId(Uuid::from_u128(240940089166493627844978703213080810552));
use crate::{
path::{AssetPath, AssetPathId},
Asset, Assets,
};
/// A unique id that corresponds to a specific asset in the [Assets](crate::Assets) collection.
/// A unique, stable asset id
#[derive(
Debug, Clone, Copy, Eq, PartialOrd, Ord, PartialEq, Hash, Serialize, Deserialize, Property,
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property,
)]
pub struct HandleId(pub Uuid);
pub enum HandleId {
Id(Uuid, u64),
AssetPathId(AssetPathId),
}
impl From<AssetPathId> for HandleId {
fn from(value: AssetPathId) -> Self {
HandleId::AssetPathId(value)
}
}
impl<'a> From<AssetPath<'a>> for HandleId {
fn from(value: AssetPath<'a>) -> Self {
HandleId::AssetPathId(AssetPathId::from(value))
}
}
impl HandleId {
#[allow(clippy::new_without_default)]
pub fn new() -> HandleId {
HandleId(Uuid::new_v4())
#[inline]
pub fn random<T: Asset>() -> Self {
HandleId::Id(T::TYPE_UUID, rand::random())
}
#[inline]
pub fn default<T: Asset>() -> Self {
HandleId::Id(T::TYPE_UUID, 0)
}
#[inline]
pub const fn new(type_uuid: Uuid, id: u64) -> Self {
HandleId::Id(type_uuid, id)
}
}
@ -36,92 +63,125 @@ where
{
pub id: HandleId,
#[property(ignore)]
handle_type: HandleType,
#[property(ignore)]
marker: PhantomData<T>,
}
enum HandleType {
Weak,
Strong(Sender<RefChange>),
}
impl Debug for HandleType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HandleType::Weak => f.write_str("Weak"),
HandleType::Strong(_) => f.write_str("Strong"),
}
}
}
impl<T> Handle<T> {
pub fn new() -> Self {
Handle {
id: HandleId::new(),
// TODO: remove "uuid" parameter whenever rust support type constraints in const fns
pub const fn weak_from_u64(uuid: Uuid, id: u64) -> Self {
Self {
id: HandleId::new(uuid, id),
handle_type: HandleType::Weak,
marker: PhantomData,
}
}
}
/// Gets a handle for the given type that has this handle's id. This is useful when an
/// asset is derived from another asset. In this case, a common handle can be used to
/// correlate them.
/// NOTE: This pattern might eventually be replaced by a more formal asset dependency system.
pub fn as_handle<U>(&self) -> Handle<U> {
Handle::from_id(self.id)
}
pub const fn from_id(id: HandleId) -> Self {
Handle {
impl<T: Asset> Handle<T> {
pub(crate) fn strong(id: HandleId, ref_change_sender: Sender<RefChange>) -> Self {
ref_change_sender.send(RefChange::Increment(id)).unwrap();
Self {
id,
handle_type: HandleType::Strong(ref_change_sender),
marker: PhantomData,
}
}
pub const fn from_u128(value: u128) -> Self {
pub fn weak(id: HandleId) -> Self {
Self {
id,
handle_type: HandleType::Weak,
marker: PhantomData,
}
}
pub fn as_weak<U>(&self) -> Handle<U> {
Handle {
id: HandleId(Uuid::from_u128(value)),
id: self.id,
handle_type: HandleType::Weak,
marker: PhantomData,
}
}
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Handle {
id: HandleId(Uuid::from_bytes(bytes)),
marker: PhantomData,
pub fn is_weak(&self) -> bool {
matches!(self.handle_type, HandleType::Weak)
}
pub fn is_strong(&self) -> bool {
matches!(self.handle_type, HandleType::Strong(_))
}
pub fn make_strong(&mut self, assets: &mut Assets<T>) {
if self.is_strong() {
return;
}
let sender = assets.ref_change_sender.clone();
sender.send(RefChange::Increment(self.id)).unwrap();
self.handle_type = HandleType::Strong(sender);
}
pub fn clone_weak(&self) -> Self {
Handle::weak(self.id)
}
pub fn clone_untyped(&self) -> HandleUntyped {
match &self.handle_type {
HandleType::Strong(sender) => HandleUntyped::strong(self.id, sender.clone()),
HandleType::Weak => HandleUntyped::weak(self.id),
}
}
pub fn from_untyped(untyped_handle: HandleUntyped) -> Option<Handle<T>>
where
T: 'static,
{
if TypeId::of::<T>() == untyped_handle.type_id {
Some(Handle::from_id(untyped_handle.id))
} else {
None
}
pub fn clone_weak_untyped(&self) -> HandleUntyped {
HandleUntyped::weak(self.id)
}
}
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(),
impl<T> Drop for Handle<T> {
fn drop(&mut self) {
match self.handle_type {
HandleType::Strong(ref sender) => {
// ignore send errors because this means the channel is shut down / the game has stopped
let _ = sender.send(RefChange::Decrement(self.id));
}
} else {
panic!("attempted to convert untyped handle to incorrect typed handle")
HandleType::Weak => {}
}
}
}
impl<T> From<Handle<T>> for HandleId {
fn from(value: Handle<T>) -> Self {
value.id
}
}
impl From<&str> for HandleId {
fn from(value: &str) -> Self {
AssetPathId::from(value).into()
}
}
impl<T> From<&Handle<T>> for HandleId {
fn from(value: &Handle<T>) -> Self {
value.id
}
}
impl<T> Hash for Handle<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
@ -148,31 +208,27 @@ impl<T> Ord for Handle<T> {
}
}
impl<T: Asset> Default for Handle<T> {
fn default() -> Self {
Handle::weak(HandleId::default::<T>())
}
}
impl<T> Debug for Handle<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let name = std::any::type_name::<T>().split("::").last().unwrap();
write!(f, "Handle<{}>({:?})", name, self.id.0)
write!(f, "{:?}Handle<{}>({:?})", self.handle_type, name, self.id)
}
}
impl<T> Default for Handle<T> {
fn default() -> Self {
Handle {
id: DEFAULT_HANDLE_ID,
marker: PhantomData,
}
}
}
impl<T> Clone for Handle<T> {
impl<T: Asset> Clone for Handle<T> {
fn clone(&self) -> Self {
Handle {
id: self.id,
marker: PhantomData,
match self.handle_type {
HandleType::Strong(ref sender) => Handle::strong(self.id, sender.clone()),
HandleType::Weak => Handle::weak(self.id),
}
}
}
impl<T> Copy for Handle<T> {}
// SAFE: T is phantom data and Handle::id is an integer
unsafe impl<T> Send for Handle<T> {}
@ -181,26 +237,115 @@ unsafe impl<T> Sync for Handle<T> {}
/// A non-generic version of [Handle]
///
/// This allows handles to be mingled in a cross asset context. For example, storing `Handle<A>` and `Handle<B>` in the same `HashSet<HandleUntyped>`.
#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)]
#[derive(Debug)]
pub struct HandleUntyped {
pub id: HandleId,
pub type_id: TypeId,
handle_type: HandleType,
}
impl HandleUntyped {
pub fn is_handle<T: 'static>(untyped: &HandleUntyped) -> bool {
TypeId::of::<T>() == untyped.type_id
pub(crate) fn strong(id: HandleId, ref_change_sender: Sender<RefChange>) -> Self {
ref_change_sender.send(RefChange::Increment(id)).unwrap();
Self {
id,
handle_type: HandleType::Strong(ref_change_sender),
}
}
}
impl<T> From<Handle<T>> for HandleUntyped
where
T: 'static,
{
fn from(handle: Handle<T>) -> Self {
HandleUntyped {
id: handle.id,
type_id: TypeId::of::<T>(),
pub fn weak(id: HandleId) -> Self {
Self {
id,
handle_type: HandleType::Weak,
}
}
pub fn clone_weak(&self) -> HandleUntyped {
HandleUntyped::weak(self.id)
}
pub fn is_weak(&self) -> bool {
matches!(self.handle_type, HandleType::Weak)
}
pub fn is_strong(&self) -> bool {
matches!(self.handle_type, HandleType::Strong(_))
}
pub fn typed<T: Asset>(mut self) -> Handle<T> {
if let HandleId::Id(type_uuid, _) = self.id {
if T::TYPE_UUID != type_uuid {
panic!("attempted to convert handle to invalid type");
}
}
let handle_type = match &self.handle_type {
HandleType::Strong(sender) => HandleType::Strong(sender.clone()),
HandleType::Weak => HandleType::Weak,
};
// ensure we don't send the RefChange event when "self" is dropped
self.handle_type = HandleType::Weak;
Handle {
handle_type,
id: self.id,
marker: PhantomData::default(),
}
}
}
impl Drop for HandleUntyped {
fn drop(&mut self) {
match self.handle_type {
HandleType::Strong(ref sender) => {
// ignore send errors because this means the channel is shut down / the game has stopped
let _ = sender.send(RefChange::Decrement(self.id));
}
HandleType::Weak => {}
}
}
}
impl From<&HandleUntyped> for HandleId {
fn from(value: &HandleUntyped) -> Self {
value.id
}
}
impl Hash for HandleUntyped {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl PartialEq for HandleUntyped {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for HandleUntyped {}
impl Clone for HandleUntyped {
fn clone(&self) -> Self {
match self.handle_type {
HandleType::Strong(ref sender) => HandleUntyped::strong(self.id, sender.clone()),
HandleType::Weak => HandleUntyped::weak(self.id),
}
}
}
pub(crate) enum RefChange {
Increment(HandleId),
Decrement(HandleId),
}
#[derive(Clone)]
pub(crate) struct RefChangeChannel {
pub sender: Sender<RefChange>,
pub receiver: Receiver<RefChange>,
}
impl Default for RefChangeChannel {
fn default() -> Self {
let (sender, receiver) = crossbeam_channel::unbounded();
RefChangeChannel { sender, receiver }
}
}

View file

@ -0,0 +1,49 @@
use crate::{path::AssetPath, LabelId};
use bevy_utils::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use uuid::Uuid;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SourceMeta {
pub assets: Vec<AssetMeta>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AssetMeta {
pub label: Option<String>,
pub dependencies: Vec<AssetPath<'static>>,
pub type_uuid: Uuid,
}
/// Info about a specific asset, such as its path and its current load state
#[derive(Clone, Debug)]
pub struct SourceInfo {
pub meta: Option<SourceMeta>,
pub path: PathBuf,
pub asset_types: HashMap<LabelId, Uuid>,
pub load_state: LoadState,
pub committed_assets: HashSet<LabelId>,
pub version: usize,
}
impl SourceInfo {
pub fn is_loaded(&self) -> bool {
self.meta.as_ref().map_or(false, |meta| {
self.committed_assets.len() == meta.assets.len()
})
}
pub fn get_asset_type(&self, label_id: LabelId) -> Option<Uuid> {
self.asset_types.get(&label_id).cloned()
}
}
/// The load state of an asset
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum LoadState {
NotLoaded,
Loading,
Loaded,
Failed,
}

168
crates/bevy_asset/src/io.rs Normal file
View file

@ -0,0 +1,168 @@
use anyhow::Result;
use bevy_ecs::Res;
use bevy_utils::HashSet;
use crossbeam_channel::TryRecvError;
use fs::File;
use io::Read;
use parking_lot::RwLock;
use std::{
env, fs, io,
path::{Path, PathBuf},
sync::Arc,
};
use thiserror::Error;
use crate::{filesystem_watcher::FilesystemWatcher, AssetServer};
/// Errors that occur while loading assets
#[derive(Error, Debug)]
pub enum AssetIoError {
#[error("Path not found")]
NotFound(PathBuf),
#[error("Encountered an io error while loading asset.")]
Io(#[from] io::Error),
#[error("Failed to watch path")]
PathWatchError(PathBuf),
}
/// Handles load requests from an AssetServer
pub trait AssetIo: Send + Sync + 'static {
fn load_path(&self, path: &Path) -> Result<Vec<u8>, AssetIoError>;
fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError>;
fn read_directory(
&self,
path: &Path,
) -> Result<Box<dyn Iterator<Item = PathBuf>>, AssetIoError>;
fn is_directory(&self, path: &Path) -> bool;
fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError>;
fn watch_for_changes(&self) -> Result<(), AssetIoError>;
}
pub struct FileAssetIo {
root_path: PathBuf,
#[cfg(feature = "filesystem_watcher")]
filesystem_watcher: Arc<RwLock<Option<FilesystemWatcher>>>,
}
impl FileAssetIo {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
FileAssetIo {
filesystem_watcher: Default::default(),
root_path: Self::get_root_path().join(path.as_ref()),
}
}
pub fn get_root_path() -> PathBuf {
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
PathBuf::from(manifest_dir)
} else {
env::current_exe()
.map(|path| {
path.parent()
.map(|exe_parent_path| exe_parent_path.to_owned())
.unwrap()
})
.unwrap()
}
}
}
impl AssetIo for FileAssetIo {
fn load_path(&self, path: &Path) -> Result<Vec<u8>, AssetIoError> {
let mut bytes = Vec::new();
match File::open(self.root_path.join(path)) {
Ok(mut file) => {
file.read_to_end(&mut bytes)?;
}
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
return Err(AssetIoError::NotFound(path.to_owned()));
} else {
return Err(e.into());
}
}
}
Ok(bytes)
}
fn read_directory(
&self,
path: &Path,
) -> Result<Box<dyn Iterator<Item = PathBuf>>, AssetIoError> {
let root_path = self.root_path.to_owned();
Ok(Box::new(fs::read_dir(root_path.join(path))?.map(
move |entry| {
let path = entry.unwrap().path();
path.strip_prefix(&root_path).unwrap().to_owned()
},
)))
}
fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError> {
let path = self.root_path.join(path);
if let Some(parent_path) = path.parent() {
fs::create_dir_all(parent_path)?;
}
Ok(fs::write(self.root_path.join(path), bytes)?)
}
fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError> {
#[cfg(feature = "filesystem_watcher")]
{
let path = self.root_path.join(path);
let mut watcher = self.filesystem_watcher.write();
if let Some(ref mut watcher) = *watcher {
watcher
.watch(&path)
.map_err(|_error| AssetIoError::PathWatchError(path))?;
}
}
Ok(())
}
fn watch_for_changes(&self) -> Result<(), AssetIoError> {
#[cfg(feature = "filesystem_watcher")]
{
*self.filesystem_watcher.write() = Some(FilesystemWatcher::default());
}
Ok(())
}
fn is_directory(&self, path: &Path) -> bool {
self.root_path.join(path).is_dir()
}
}
#[cfg(feature = "filesystem_watcher")]
pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
let mut changed = HashSet::default();
let watcher = asset_server.server.asset_io.filesystem_watcher.read();
if let Some(ref watcher) = *watcher {
loop {
let event = match watcher.receiver.try_recv() {
Ok(result) => result.unwrap(),
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => panic!("FilesystemWatcher disconnected"),
};
if let notify::event::Event {
kind: notify::event::EventKind::Modify(_),
paths,
..
} = event
{
for path in paths.iter() {
if !changed.contains(path) {
let relative_path = path
.strip_prefix(&asset_server.server.asset_io.root_path)
.unwrap();
let _ = asset_server.load_untracked(relative_path, true);
}
}
changed.extend(paths);
}
}
}
}

View file

@ -3,15 +3,19 @@ mod assets;
#[cfg(feature = "filesystem_watcher")]
mod filesystem_watcher;
mod handle;
mod load_request;
mod info;
mod io;
mod loader;
mod path;
pub use asset_server::*;
pub use assets::*;
use bevy_tasks::IoTaskPool;
pub use handle::*;
pub use load_request::*;
pub use info::*;
pub use io::*;
pub use loader::*;
pub use path::*;
/// The names of asset stages in an App Schedule
pub mod stage {
@ -20,7 +24,7 @@ pub mod stage {
}
pub mod prelude {
pub use crate::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
pub use crate::{AddAsset, AssetEvent, AssetServer, Assets, Handle, HandleUntyped};
}
use bevy_app::{prelude::Plugin, AppBuilder};
@ -32,6 +36,18 @@ use bevy_type_registry::RegisterType;
#[derive(Default)]
pub struct AssetPlugin;
pub struct AssetServerSettings {
asset_folder: String,
}
impl Default for AssetServerSettings {
fn default() -> Self {
Self {
asset_folder: "assets".to_string(),
}
}
}
impl Plugin for AssetPlugin {
fn build(&self, app: &mut AppBuilder) {
let task_pool = app
@ -40,15 +56,25 @@ impl Plugin for AssetPlugin {
.expect("IoTaskPool resource not found")
.0
.clone();
let asset_server = {
let settings = app
.resources_mut()
.get_or_insert_with(AssetServerSettings::default);
let source = FileAssetIo::new(&settings.asset_folder);
AssetServer::new(source, task_pool)
};
app.add_stage_before(bevy_app::stage::PRE_UPDATE, stage::LOAD_ASSETS)
.add_stage_after(bevy_app::stage::POST_UPDATE, stage::ASSET_EVENTS)
.add_resource(AssetServer::new(task_pool))
.register_property::<HandleId>();
.add_resource(asset_server)
.register_property::<HandleId>()
.add_system_to_stage(
bevy_app::stage::PRE_UPDATE,
asset_server::free_unused_assets_system.system(),
);
#[cfg(feature = "filesystem_watcher")]
app.add_system_to_stage(
stage::LOAD_ASSETS,
asset_server::filesystem_watcher_system.system(),
);
app.add_system_to_stage(stage::LOAD_ASSETS, io::filesystem_watcher_system.system());
}
}

View file

@ -1,31 +0,0 @@
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,
}

View file

@ -1,62 +0,0 @@
use super::{ChannelAssetHandler, LoadRequest};
use crate::{AssetLoadError, AssetLoader, AssetResult, Handle};
use anyhow::Result;
use async_trait::async_trait;
use crossbeam_channel::Sender;
use std::{fs::File, io::Read};
/// Handles load requests from an AssetServer
#[async_trait]
pub trait AssetLoadRequestHandler: Send + Sync + 'static {
async fn handle_request(&self, load_request: &LoadRequest);
fn extensions(&self) -> &[&str];
}
impl<TLoader, TAsset> ChannelAssetHandler<TLoader, TAsset>
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> {
match File::open(&load_request.path) {
Ok(mut file) => {
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
let asset = self.loader.from_bytes(&load_request.path, bytes)?;
Ok(asset)
}
Err(e) => Err(AssetLoadError::Io(std::io::Error::new(
e.kind(),
format!("{}", load_request.path.display()),
))),
}
}
}
#[async_trait]
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 result = self.load_asset(load_request);
let asset_result = AssetResult {
handle: Handle::from(load_request.handle_id),
result,
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()
}
}

View file

@ -1,62 +0,0 @@
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()
}
}

View file

@ -1,85 +1,173 @@
use crate::{AssetServer, AssetVersion, Assets, Handle, LoadState};
use crate::{
path::AssetPath, AssetIo, AssetIoError, AssetMeta, AssetServer, Assets, Handle, HandleId,
RefChangeChannel,
};
use anyhow::Result;
use bevy_ecs::{Res, ResMut, Resource};
use crossbeam_channel::{Receiver, Sender, TryRecvError};
use fs::File;
use io::Read;
use std::{
fs, io,
path::{Path, PathBuf},
};
use thiserror::Error;
use bevy_type_registry::{TypeUuid, TypeUuidDynamic};
use bevy_utils::HashMap;
use crossbeam_channel::{Receiver, Sender};
use downcast_rs::{impl_downcast, Downcast};
use std::path::Path;
/// Errors that occur while loading assets
#[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),
/// A loader for an asset source
pub trait AssetLoader: Send + Sync + 'static {
fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<(), anyhow::Error>;
fn extensions(&self) -> &[&str];
}
/// A loader for a given asset of type `T`
pub trait AssetLoader<T>: Send + Sync + 'static {
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<T, anyhow::Error>;
fn extensions(&self) -> &[&str];
fn load_from_file(&self, asset_path: &Path) -> Result<T, AssetLoadError> {
let mut file = File::open(asset_path)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
let asset = self.from_bytes(asset_path, bytes)?;
Ok(asset)
pub trait Asset: TypeUuid + AssetDynamic {}
pub trait AssetDynamic: Downcast + TypeUuidDynamic + Send + Sync + 'static {}
impl_downcast!(AssetDynamic);
impl<T> Asset for T where T: TypeUuid + AssetDynamic + TypeUuidDynamic {}
impl<T> AssetDynamic for T where T: Send + Sync + 'static + TypeUuidDynamic {}
pub struct LoadedAsset {
pub(crate) value: Option<Box<dyn AssetDynamic>>,
pub(crate) dependencies: Vec<AssetPath<'static>>,
}
impl LoadedAsset {
pub fn new<T: Asset>(value: T) -> Self {
Self {
value: Some(Box::new(value)),
dependencies: Vec::new(),
}
}
pub fn with_dependency(mut self, asset_path: AssetPath) -> Self {
self.dependencies.push(asset_path.to_owned());
self
}
pub fn with_dependencies(mut self, asset_paths: Vec<AssetPath<'static>>) -> Self {
self.dependencies.extend(asset_paths);
self
}
}
pub struct LoadContext<'a> {
pub(crate) ref_change_channel: &'a RefChangeChannel,
pub(crate) asset_io: &'a dyn AssetIo,
pub(crate) labeled_assets: HashMap<Option<String>, LoadedAsset>,
pub(crate) path: &'a Path,
pub(crate) version: usize,
}
impl<'a> LoadContext<'a> {
pub(crate) fn new(
path: &'a Path,
ref_change_channel: &'a RefChangeChannel,
asset_io: &'a dyn AssetIo,
version: usize,
) -> Self {
Self {
ref_change_channel,
asset_io,
labeled_assets: Default::default(),
version,
path,
}
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn has_labeled_asset(&self, label: &str) -> bool {
self.labeled_assets.contains_key(&Some(label.to_string()))
}
pub fn set_default_asset(&mut self, asset: LoadedAsset) {
self.labeled_assets.insert(None, asset);
}
pub fn set_labeled_asset(&mut self, label: &str, asset: LoadedAsset) {
assert!(!label.is_empty());
self.labeled_assets.insert(Some(label.to_string()), asset);
}
pub fn get_handle<I: Into<HandleId>, T: Asset>(&self, id: I) -> Handle<T> {
Handle::strong(id.into(), self.ref_change_channel.sender.clone())
}
pub fn read_asset_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
self.asset_io.load_path(path.as_ref())
}
pub fn get_asset_metas(&self) -> Vec<AssetMeta> {
let mut asset_metas = Vec::new();
for (label, asset) in self.labeled_assets.iter() {
asset_metas.push(AssetMeta {
dependencies: asset.dependencies.clone(),
label: label.clone(),
type_uuid: asset.value.as_ref().unwrap().type_uuid(),
});
}
asset_metas
}
}
/// The result of loading an asset of type `T`
#[derive(Debug)]
pub struct AssetResult<T: 'static> {
pub result: Result<T, AssetLoadError>,
pub handle: Handle<T>,
pub path: PathBuf,
pub version: AssetVersion,
pub struct AssetResult<T: Resource> {
pub asset: T,
pub id: HandleId,
pub version: usize,
}
/// A channel to send and receive [AssetResult]s
#[derive(Debug)]
pub struct AssetChannel<T: 'static> {
pub sender: Sender<AssetResult<T>>,
pub receiver: Receiver<AssetResult<T>>,
pub struct AssetLifecycleChannel<T: Resource> {
pub sender: Sender<AssetLifecycleEvent<T>>,
pub receiver: Receiver<AssetLifecycleEvent<T>>,
}
impl<T> AssetChannel<T> {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
pub enum AssetLifecycleEvent<T: Resource> {
Create(AssetResult<T>),
Free(HandleId),
}
pub trait AssetLifecycle: Downcast + Send + Sync + 'static {
fn create_asset(&self, id: HandleId, asset: Box<dyn AssetDynamic>, version: usize);
fn free_asset(&self, id: HandleId);
}
impl_downcast!(AssetLifecycle);
impl<T: AssetDynamic> AssetLifecycle for AssetLifecycleChannel<T> {
fn create_asset(&self, id: HandleId, asset: Box<dyn AssetDynamic>, version: usize) {
if let Ok(asset) = asset.downcast::<T>() {
self.sender
.send(AssetLifecycleEvent::Create(AssetResult {
id,
asset: *asset,
version,
}))
.unwrap()
} else {
panic!("failed to downcast asset to {}", std::any::type_name::<T>());
}
}
fn free_asset(&self, id: HandleId) {
self.sender.send(AssetLifecycleEvent::Free(id)).unwrap();
}
}
impl<T: Resource> Default for AssetLifecycleChannel<T> {
fn default() -> Self {
let (sender, receiver) = crossbeam_channel::unbounded();
AssetChannel { sender, receiver }
AssetLifecycleChannel { sender, receiver }
}
}
/// Reads [AssetResult]s from an [AssetChannel] and updates the [Assets] collection and [LoadState] accordingly
pub fn update_asset_storage_system<T: Resource>(
asset_channel: Res<AssetChannel<T>>,
pub fn update_asset_storage_system<T: Asset + AssetDynamic>(
asset_server: Res<AssetServer>,
mut assets: ResMut<Assets<T>>,
) {
loop {
match asset_channel.receiver.try_recv() {
Ok(result) => match result.result {
Ok(asset) => {
assets.set(result.handle, asset);
asset_server
.set_load_state(result.handle.id, LoadState::Loaded(result.version));
}
Err(err) => {
asset_server
.set_load_state(result.handle.id, LoadState::Failed(result.version));
log::error!("Failed to load asset: {:?}", err);
}
},
Err(TryRecvError::Empty) => {
break;
}
Err(TryRecvError::Disconnected) => panic!("AssetChannel disconnected"),
}
}
asset_server.update_asset_storage(&mut assets);
}

View file

@ -0,0 +1,168 @@
use bevy_property::Property;
use bevy_utils::AHasher;
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
hash::{Hash, Hasher},
path::{Path, PathBuf},
};
#[derive(Debug, Hash, Clone, Serialize, Deserialize)]
pub struct AssetPath<'a> {
path: Cow<'a, Path>,
label: Option<Cow<'a, str>>,
}
impl<'a> AssetPath<'a> {
#[inline]
pub fn new_ref(path: &'a Path, label: Option<&'a str>) -> AssetPath<'a> {
AssetPath {
path: Cow::Borrowed(path),
label: label.map(|val| Cow::Borrowed(val)),
}
}
#[inline]
pub fn new(path: PathBuf, label: Option<String>) -> AssetPath<'a> {
AssetPath {
path: Cow::Owned(path),
label: label.map(Cow::Owned),
}
}
#[inline]
pub fn get_id(&self) -> AssetPathId {
AssetPathId::from(self)
}
#[inline]
pub fn label(&self) -> Option<&str> {
self.label.as_ref().map(|label| label.as_ref())
}
#[inline]
pub fn path(&self) -> &Path {
&self.path
}
#[inline]
pub fn to_owned(&self) -> AssetPath<'static> {
AssetPath {
path: Cow::Owned(self.path.to_path_buf()),
label: self
.label
.as_ref()
.map(|value| Cow::Owned(value.to_string())),
}
}
}
#[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property,
)]
pub struct AssetPathId(SourcePathId, LabelId);
#[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property,
)]
pub struct SourcePathId(u64);
#[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property,
)]
pub struct LabelId(u64);
impl<'a> From<&'a Path> for SourcePathId {
fn from(value: &'a Path) -> Self {
let mut hasher = get_hasher();
value.hash(&mut hasher);
SourcePathId(hasher.finish())
}
}
impl From<AssetPathId> for SourcePathId {
fn from(id: AssetPathId) -> Self {
id.source_path_id()
}
}
impl<'a> From<AssetPath<'a>> for SourcePathId {
fn from(path: AssetPath) -> Self {
AssetPathId::from(path).source_path_id()
}
}
impl<'a> From<Option<&'a str>> for LabelId {
fn from(value: Option<&'a str>) -> Self {
let mut hasher = get_hasher();
value.hash(&mut hasher);
LabelId(hasher.finish())
}
}
impl AssetPathId {
pub fn source_path_id(&self) -> SourcePathId {
self.0
}
pub fn label_id(&self) -> LabelId {
self.1
}
}
/// this hasher provides consistent results across runs
pub(crate) fn get_hasher() -> AHasher {
AHasher::new_with_keys(42, 23)
}
impl<'a, T> From<T> for AssetPathId
where
T: Into<AssetPath<'a>>,
{
fn from(value: T) -> Self {
let asset_path: AssetPath = value.into();
AssetPathId(
SourcePathId::from(asset_path.path()),
LabelId::from(asset_path.label()),
)
}
}
impl<'a, 'b> From<&'a AssetPath<'b>> for AssetPathId {
fn from(asset_path: &'a AssetPath<'b>) -> Self {
AssetPathId(
SourcePathId::from(asset_path.path()),
LabelId::from(asset_path.label()),
)
}
}
impl<'a> From<&'a str> for AssetPath<'a> {
fn from(asset_path: &'a str) -> Self {
let mut parts = asset_path.split('#');
let path = Path::new(parts.next().expect("path must be set"));
let label = parts.next();
AssetPath {
path: Cow::Borrowed(path),
label: label.map(|label| Cow::Borrowed(label)),
}
}
}
impl<'a> From<&'a Path> for AssetPath<'a> {
fn from(path: &'a Path) -> Self {
AssetPath {
path: Cow::Borrowed(path),
label: None,
}
}
}
impl<'a> From<PathBuf> for AssetPath<'a> {
fn from(path: PathBuf) -> Self {
AssetPath {
path: Cow::Owned(path),
label: None,
}
}
}

View file

@ -17,6 +17,7 @@ keywords = ["bevy"]
bevy_app = { path = "../bevy_app", version = "0.2.1" }
bevy_asset = { path = "../bevy_asset", version = "0.2.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.2.1" }
bevy_type_registry = { path = "../bevy_type_registry", version = "0.2.1" }
# other
anyhow = "1.0"

View file

@ -1,5 +1,5 @@
use crate::{AudioSource, Decodable};
use bevy_asset::{Assets, Handle};
use bevy_asset::{Asset, Assets, Handle};
use bevy_ecs::Res;
use parking_lot::RwLock;
use rodio::{Device, Sink};
@ -39,7 +39,7 @@ where
impl<P> AudioOutput<P>
where
P: Decodable,
P: Asset + Decodable,
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,
{
@ -71,8 +71,10 @@ where
}
/// Plays audio currently queued in the [AudioOutput] resource
pub fn play_queued_audio_system<P>(audio_sources: Res<Assets<P>>, audio_output: Res<AudioOutput<P>>)
where
pub fn play_queued_audio_system<P: Asset>(
audio_sources: Res<Assets<P>>,
audio_output: Res<AudioOutput<P>>,
) where
P: Decodable,
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,

View file

@ -1,9 +1,11 @@
use anyhow::Result;
use bevy_asset::AssetLoader;
use std::{io::Cursor, path::Path, sync::Arc};
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
use bevy_type_registry::TypeUuid;
use std::{io::Cursor, sync::Arc};
/// A source of audio data
#[derive(Debug, Clone)]
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "7a14806a-672b-443b-8d16-4f18afefa463"]
pub struct AudioSource {
pub bytes: Arc<[u8]>,
}
@ -18,11 +20,12 @@ impl AsRef<[u8]> for AudioSource {
#[derive(Default)]
pub struct Mp3Loader;
impl AssetLoader<AudioSource> for Mp3Loader {
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<AudioSource> {
Ok(AudioSource {
impl AssetLoader for Mp3Loader {
fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> {
load_context.set_default_asset(LoadedAsset::new(AudioSource {
bytes: bytes.into(),
})
}));
Ok(())
}
fn extensions(&self) -> &[&str] {

View file

@ -20,7 +20,7 @@ impl Plugin for AudioPlugin {
fn build(&self, app: &mut AppBuilder) {
app.init_resource::<AudioOutput<AudioSource>>()
.add_asset::<AudioSource>()
.add_asset_loader::<AudioSource, Mp3Loader>()
.init_asset_loader::<Mp3Loader>()
.add_system_to_stage(
stage::POST_UPDATE,
play_queued_audio_system::<AudioSource>.system(),

View file

@ -21,3 +21,4 @@ proc-macro-crate = "0.1.5"
proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0"
uuid = { version = "0.8", features = ["v4", "serde"] }

View file

@ -8,6 +8,7 @@ mod render_resource;
mod render_resources;
mod resource;
mod shader_defs;
mod type_uuid;
use proc_macro::TokenStream;
@ -55,3 +56,14 @@ pub fn derive_as_vertex_buffer_descriptor(input: TokenStream) -> TokenStream {
pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream {
app_plugin::derive_dynamic_plugin(input)
}
// From https://github.com/randomPoison/type-uuid
#[proc_macro_derive(TypeUuid, attributes(uuid))]
pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
type_uuid::type_uuid_derive(input)
}
#[proc_macro]
pub fn external_type_uuid(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
type_uuid::external_type_uuid(tokens)
}

View file

@ -8,6 +8,7 @@ pub struct Modules {
pub bevy_asset: String,
pub bevy_core: String,
pub bevy_app: String,
pub bevy_type_registry: String,
}
impl Modules {
@ -17,6 +18,7 @@ impl Modules {
bevy_render: "bevy::render".to_string(),
bevy_core: "bevy::core".to_string(),
bevy_app: "bevy::app".to_string(),
bevy_type_registry: "bevy::type_registry".to_string(),
}
}
@ -26,6 +28,7 @@ impl Modules {
bevy_render: "bevy_render".to_string(),
bevy_core: "bevy_core".to_string(),
bevy_app: "bevy_app".to_string(),
bevy_type_registry: "bevy_type_registry".to_string(),
}
}
}

View file

@ -25,7 +25,7 @@ pub fn derive_render_resource(input: TokenStream) -> TokenStream {
use #bevy_core_path::Bytes;
Some(self.byte_len())
}
fn texture(&self) -> Option<#bevy_asset_path::Handle<#bevy_render_path::texture::Texture>> {
fn texture(&self) -> Option<&#bevy_asset_path::Handle<#bevy_render_path::texture::Texture>> {
None
}

View file

@ -0,0 +1,98 @@
extern crate proc_macro;
use quote::quote;
use syn::{parse::*, *};
use uuid::Uuid;
use crate::modules::{get_modules, get_path};
pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast: DeriveInput = syn::parse(input).unwrap();
let modules = get_modules(&ast.attrs);
let bevy_type_registry_path: Path = get_path(&modules.bevy_type_registry);
// Build the trait implementation
let name = &ast.ident;
let mut uuid = None;
for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
let name_value = if let Meta::NameValue(name_value) = attribute {
name_value
} else {
continue;
};
if name_value
.path
.get_ident()
.map(|i| i != "uuid")
.unwrap_or(true)
{
continue;
}
let uuid_str = match name_value.lit {
Lit::Str(lit_str) => lit_str,
_ => panic!("uuid attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"`"),
};
uuid = Some(
Uuid::parse_str(&uuid_str.value())
.expect("Value specified to `#[uuid]` attribute is not a valid UUID"),
);
}
let uuid =
uuid.expect("No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"` attribute found");
let bytes = uuid
.as_bytes()
.iter()
.map(|byte| format!("{:#X}", byte))
.map(|byte_str| syn::parse_str::<LitInt>(&byte_str).unwrap());
let gen = quote! {
impl #bevy_type_registry_path::TypeUuid for #name {
const TYPE_UUID: #bevy_type_registry_path::Uuid = #bevy_type_registry_path::Uuid::from_bytes([
#( #bytes ),*
]);
}
};
gen.into()
}
struct ExternalDeriveInput {
path: ExprPath,
uuid_str: LitStr,
}
impl Parse for ExternalDeriveInput {
fn parse(input: ParseStream) -> Result<Self> {
let path = input.parse()?;
input.parse::<Token![,]>()?;
let uuid_str = input.parse()?;
Ok(Self { path, uuid_str })
}
}
pub fn external_type_uuid(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ExternalDeriveInput { path, uuid_str } = parse_macro_input!(tokens as ExternalDeriveInput);
let uuid = Uuid::parse_str(&uuid_str.value()).expect("Value was not a valid UUID");
let bytes = uuid
.as_bytes()
.iter()
.map(|byte| format!("{:#X}", byte))
.map(|byte_str| syn::parse_str::<LitInt>(&byte_str).unwrap());
let gen = quote! {
impl crate::TypeUuid for #path {
const TYPE_UUID: Uuid = uuid::Uuid::from_bytes([
#( #bytes ),*
]);
}
};
gen.into()
}

View file

@ -21,6 +21,7 @@ bevy_hecs = { path = "hecs", features = ["macros", "serialize"], version = "0.2.
bevy_tasks = { path = "../bevy_tasks", version = "0.2.1" }
bevy_utils = { path = "../bevy_utils", version = "0.2.1" }
rand = "0.7.3"
thiserror = "1.0"
fixedbitset = "0.3.1"
downcast-rs = "1.2.0"
parking_lot = "0.11.0"

View file

@ -157,6 +157,18 @@ impl Resources {
})
}
pub fn get_or_insert_with<T: Resource>(
&mut self,
get_resource: impl FnOnce() -> T,
) -> RefMut<'_, T> {
// NOTE: this double-get is really weird. why cant we use an if-let here?
if self.get::<T>().is_some() {
return self.get_mut::<T>().unwrap();
}
self.insert(get_resource());
self.get_mut().unwrap()
}
/// Returns a clone of the underlying resource, this is helpful when borrowing something
/// cloneable (like a task pool) without taking a borrow on the resource map
pub fn get_cloned<T: Resource + Clone>(&self) -> Option<T> {

View file

@ -2,32 +2,11 @@ use super::SystemId;
use crate::resource::{Resource, Resources};
use bevy_hecs::{Bundle, Component, DynamicBundle, Entity, EntityReserver, World};
use parking_lot::Mutex;
use std::{fmt, marker::PhantomData, sync::Arc};
/// A queued command to mutate the current [World] or [Resources]
pub enum Command {
WriteWorld(Box<dyn WorldWriter>),
WriteResources(Box<dyn ResourcesWriter>),
}
impl fmt::Debug for Command {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Command::WriteWorld(x) => f
.debug_tuple("WriteWorld")
.field(&(x.as_ref() as *const dyn WorldWriter))
.finish(),
Command::WriteResources(x) => f
.debug_tuple("WriteResources")
.field(&(x.as_ref() as *const dyn ResourcesWriter))
.finish(),
}
}
}
use std::{marker::PhantomData, sync::Arc};
/// A [World] mutation
pub trait WorldWriter: Send + Sync {
fn write(self: Box<Self>, world: &mut World);
pub trait Command: Send + Sync {
fn write(self: Box<Self>, world: &mut World, resources: &mut Resources);
}
#[derive(Debug)]
@ -38,11 +17,11 @@ where
components: T,
}
impl<T> WorldWriter for Spawn<T>
impl<T> Command for Spawn<T>
where
T: DynamicBundle + Send + Sync + 'static,
{
fn write(self: Box<Self>, world: &mut World) {
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
world.spawn(self.components);
}
}
@ -55,12 +34,12 @@ where
components_iter: I,
}
impl<I> WorldWriter for SpawnBatch<I>
impl<I> Command for SpawnBatch<I>
where
I: IntoIterator + Send + Sync,
I::Item: Bundle,
{
fn write(self: Box<Self>, world: &mut World) {
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
world.spawn_batch(self.components_iter);
}
}
@ -70,8 +49,8 @@ pub(crate) struct Despawn {
entity: Entity,
}
impl WorldWriter for Despawn {
fn write(self: Box<Self>, world: &mut World) {
impl Command for Despawn {
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
if let Err(e) = world.despawn(self.entity) {
log::debug!("Failed to despawn entity {:?}: {}", self.entity, e);
}
@ -86,11 +65,11 @@ where
components: T,
}
impl<T> WorldWriter for Insert<T>
impl<T> Command for Insert<T>
where
T: DynamicBundle + Send + Sync + 'static,
{
fn write(self: Box<Self>, world: &mut World) {
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
world.insert(self.entity, self.components).unwrap();
}
}
@ -104,11 +83,11 @@ where
component: T,
}
impl<T> WorldWriter for InsertOne<T>
impl<T> Command for InsertOne<T>
where
T: Component,
{
fn write(self: Box<Self>, world: &mut World) {
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
world.insert(self.entity, (self.component,)).unwrap();
}
}
@ -122,11 +101,11 @@ where
phantom: PhantomData<T>,
}
impl<T> WorldWriter for RemoveOne<T>
impl<T> Command for RemoveOne<T>
where
T: Component,
{
fn write(self: Box<Self>, world: &mut World) {
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
if world.get::<T>(self.entity).is_ok() {
world.remove_one::<T>(self.entity).unwrap();
}
@ -142,11 +121,11 @@ where
phantom: PhantomData<T>,
}
impl<T> WorldWriter for Remove<T>
impl<T> Command for Remove<T>
where
T: Bundle + Send + Sync + 'static,
{
fn write(self: Box<Self>, world: &mut World) {
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
world.remove::<T>(self.entity).unwrap();
}
}
@ -159,8 +138,8 @@ pub struct InsertResource<T: Resource> {
resource: T,
}
impl<T: Resource> ResourcesWriter for InsertResource<T> {
fn write(self: Box<Self>, resources: &mut Resources) {
impl<T: Resource> Command for InsertResource<T> {
fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
resources.insert(self.resource);
}
}
@ -171,15 +150,15 @@ pub(crate) struct InsertLocalResource<T: Resource> {
system_id: SystemId,
}
impl<T: Resource> ResourcesWriter for InsertLocalResource<T> {
fn write(self: Box<Self>, resources: &mut Resources) {
impl<T: Resource> Command for InsertLocalResource<T> {
fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
resources.insert_local(self.system_id, self.resource);
}
}
#[derive(Debug, Default)]
#[derive(Default)]
pub struct CommandsInternal {
pub commands: Vec<Command>,
pub commands: Vec<Box<dyn Command>>,
pub current_entity: Option<Entity>,
pub entity_reserver: Option<EntityReserver>,
}
@ -192,8 +171,7 @@ impl CommandsInternal {
.expect("entity reserver has not been set")
.reserve_entity();
self.current_entity = Some(entity);
self.commands
.push(Command::WriteWorld(Box::new(Insert { entity, components })));
self.commands.push(Box::new(Insert { entity, components }));
self
}
@ -202,43 +180,35 @@ impl CommandsInternal {
components: impl DynamicBundle + Send + Sync + 'static,
) -> &mut Self {
let current_entity = self.current_entity.expect("Cannot add components because the 'current entity' is not set. You should spawn an entity first.");
self.commands.push(Command::WriteWorld(Box::new(Insert {
self.commands.push(Box::new(Insert {
entity: current_entity,
components,
})));
}));
self
}
pub fn with(&mut self, component: impl Component) -> &mut Self {
let current_entity = self.current_entity.expect("Cannot add component because the 'current entity' is not set. You should spawn an entity first.");
self.commands.push(Command::WriteWorld(Box::new(InsertOne {
self.commands.push(Box::new(InsertOne {
entity: current_entity,
component,
})));
}));
self
}
pub fn write_world<W: WorldWriter + 'static>(&mut self, world_writer: W) -> &mut Self {
self.write_world_boxed(Box::new(world_writer))
}
pub fn write_world_boxed(&mut self, world_writer: Box<dyn WorldWriter + 'static>) -> &mut Self {
self.commands.push(Command::WriteWorld(world_writer));
pub fn add_command<C: Command + 'static>(&mut self, command: C) -> &mut Self {
self.commands.push(Box::new(command));
self
}
pub fn write_resources<W: ResourcesWriter + 'static>(
&mut self,
resources_writer: W,
) -> &mut Self {
self.commands
.push(Command::WriteResources(Box::new(resources_writer)));
pub fn add_command_boxed(&mut self, command: Box<dyn Command>) -> &mut Self {
self.commands.push(command);
self
}
}
/// A queue of [Command]s to run on the current [World] and [Resources]
#[derive(Debug, Default, Clone)]
#[derive(Default, Clone)]
pub struct Commands {
pub commands: Arc<Mutex<CommandsInternal>>,
}
@ -257,12 +227,12 @@ impl Commands {
I: IntoIterator + Send + Sync + 'static,
I::Item: Bundle,
{
self.write_world(SpawnBatch { components_iter })
self.add_command(SpawnBatch { components_iter })
}
/// Despawns only the specified entity, ignoring any other consideration.
pub fn despawn(&mut self, entity: Entity) -> &mut Self {
self.write_world(Despawn { entity })
self.add_command(Despawn { entity })
}
pub fn with(&mut self, component: impl Component) -> &mut Self {
@ -289,15 +259,15 @@ impl Commands {
entity: Entity,
components: impl DynamicBundle + Send + Sync + 'static,
) -> &mut Self {
self.write_world(Insert { entity, components })
self.add_command(Insert { entity, components })
}
pub fn insert_one(&mut self, entity: Entity, component: impl Component) -> &mut Self {
self.write_world(InsertOne { entity, component })
self.add_command(InsertOne { entity, component })
}
pub fn insert_resource<T: Resource>(&mut self, resource: T) -> &mut Self {
self.write_resources(InsertResource { resource })
self.add_command(InsertResource { resource })
}
pub fn insert_local_resource<T: Resource>(
@ -305,39 +275,26 @@ impl Commands {
system_id: SystemId,
resource: T,
) -> &mut Self {
self.write_resources(InsertLocalResource {
self.add_command(InsertLocalResource {
system_id,
resource,
})
}
pub fn write_world<W: WorldWriter + 'static>(&mut self, world_writer: W) -> &mut Self {
self.commands.lock().write_world(world_writer);
pub fn add_command<C: Command + 'static>(&mut self, command: C) -> &mut Self {
self.commands.lock().add_command(command);
self
}
pub fn write_world_boxed(&mut self, world_writer: Box<dyn WorldWriter + 'static>) -> &mut Self {
self.commands.lock().write_world_boxed(world_writer);
self
}
pub fn write_resources<W: ResourcesWriter + 'static>(
&mut self,
resources_writer: W,
) -> &mut Self {
self.commands.lock().write_resources(resources_writer);
pub fn add_command_boxed(&mut self, command: Box<dyn Command>) -> &mut Self {
self.commands.lock().add_command_boxed(command);
self
}
pub fn apply(&self, world: &mut World, resources: &mut Resources) {
let mut commands = self.commands.lock();
for command in commands.commands.drain(..) {
match command {
Command::WriteWorld(writer) => {
writer.write(world);
}
Command::WriteResources(writer) => writer.write(resources),
}
command.write(world, resources);
}
}
@ -361,7 +318,7 @@ impl Commands {
where
T: Component,
{
self.write_world(RemoveOne::<T> {
self.add_command(RemoveOne::<T> {
entity,
phantom: PhantomData,
})
@ -371,7 +328,7 @@ impl Commands {
where
T: Bundle + Send + Sync + 'static,
{
self.write_world(Remove::<T> {
self.add_command(Remove::<T> {
entity,
phantom: PhantomData,
})

View file

@ -0,0 +1,49 @@
use std::collections::hash_map::Entry;
use bevy_hecs::Entity;
use bevy_utils::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MapEntitiesError {
#[error("The given entity does not exist in the map.")]
EntityNotFound(Entity),
}
pub trait MapEntities {
fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError>;
}
#[derive(Default, Debug)]
pub struct EntityMap {
map: HashMap<Entity, Entity>,
}
impl EntityMap {
pub fn insert(&mut self, from: Entity, to: Entity) {
self.map.insert(from, to);
}
pub fn remove(&mut self, entity: Entity) {
self.map.remove(&entity);
}
pub fn entry(&mut self, entity: Entity) -> Entry<'_, Entity, Entity> {
self.map.entry(entity)
}
pub fn get(&self, entity: Entity) -> Result<Entity, MapEntitiesError> {
self.map
.get(&entity)
.cloned()
.ok_or(MapEntitiesError::EntityNotFound(entity))
}
pub fn keys(&self) -> impl Iterator<Item = Entity> + '_ {
self.map.keys().cloned()
}
pub fn values(&self) -> impl Iterator<Item = Entity> + '_ {
self.map.values().cloned()
}
}

View file

@ -1,3 +1,5 @@
mod entity_map;
mod world_builder;
pub use entity_map::*;
pub use world_builder::*;

View file

@ -16,10 +16,17 @@ keywords = ["bevy"]
# bevy
bevy_app = { path = "../bevy_app", version = "0.2.1" }
bevy_asset = { path = "../bevy_asset", version = "0.2.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.2.1" }
bevy_pbr = { path = "../bevy_pbr", version = "0.2.1" }
bevy_render = { path = "../bevy_render", version = "0.2.1" }
bevy_transform = { path = "../bevy_transform", version = "0.2.1" }
bevy_math = { path = "../bevy_math", version = "0.2.1" }
bevy_scene = { path = "../bevy_scene", version = "0.2.1" }
bevy_type_registry = { path = "../bevy_type_registry", version = "0.2.1" }
# other
gltf = { version = "0.15.2", default-features = false, features = ["utils"] }
image = { version = "0.23", default-features = false }
thiserror = "1.0"
anyhow = "1.0"
base64 = "0.12.3"

View file

@ -3,7 +3,6 @@ pub use loader::*;
use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_render::mesh::Mesh;
/// Adds support for GLTF file loading to Apps
#[derive(Default)]
@ -11,6 +10,6 @@ pub struct GltfPlugin;
impl Plugin for GltfPlugin {
fn build(&self, app: &mut AppBuilder) {
app.add_asset_loader::<Mesh, GltfLoader>();
app.init_asset_loader::<GltfLoader>();
}
}

View file

@ -1,32 +1,24 @@
use anyhow::Result;
use bevy_asset::{AssetIoError, AssetLoader, AssetPath, LoadContext, LoadedAsset};
use bevy_ecs::{World, WorldBuilderSource};
use bevy_math::Mat4;
use bevy_pbr::prelude::{PbrComponents, StandardMaterial};
use bevy_render::{
mesh::{Indices, Mesh, VertexAttribute},
pipeline::PrimitiveTopology,
prelude::{Color, Texture},
texture::TextureFormat,
};
use anyhow::Result;
use bevy_asset::AssetLoader;
use gltf::{buffer::Source, mesh::Mode};
use std::{fs, io, path::Path};
use bevy_scene::Scene;
use bevy_transform::{
hierarchy::{BuildWorldChildren, WorldChildBuilder},
prelude::{GlobalTransform, Transform},
};
use gltf::{mesh::Mode, Primitive};
use image::{GenericImageView, ImageFormat};
use std::path::Path;
use thiserror::Error;
/// Loads meshes from GLTF files into Mesh assets
///
/// NOTE: eventually this will loading into Scenes instead of Meshes
#[derive(Debug, Default)]
pub struct GltfLoader;
impl AssetLoader<Mesh> for GltfLoader {
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh> {
let mesh = load_gltf(asset_path, bytes)?;
Ok(mesh)
}
fn extensions(&self) -> &[&str] {
static EXTENSIONS: &[&str] = &["gltf", "glb"];
EXTENSIONS
}
}
/// An error that occurs when loading a GLTF file
#[derive(Error, Debug)]
pub enum GltfError {
@ -34,14 +26,234 @@ pub enum GltfError {
UnsupportedPrimitive { mode: Mode },
#[error("Invalid GLTF file.")]
Gltf(#[from] gltf::Error),
#[error("Failed to load file.")]
Io(#[from] io::Error),
#[error("Binary blob is missing.")]
MissingBlob,
#[error("Failed to decode base64 mesh data.")]
Base64Decode(#[from] base64::DecodeError),
#[error("Unsupported buffer format.")]
BufferFormatUnsupported,
#[error("Invalid image mime type.")]
InvalidImageMimeType(String),
#[error("Failed to convert image to rgb8.")]
ImageRgb8ConversionFailure,
#[error("Failed to load an image.")]
ImageError(#[from] image::ImageError),
#[error("Failed to load an asset path.")]
AssetIoError(#[from] AssetIoError),
}
/// Loads meshes from GLTF files into Mesh assets
#[derive(Default)]
pub struct GltfLoader;
impl AssetLoader for GltfLoader {
fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> {
Ok(load_gltf(bytes, load_context)?)
}
fn extensions(&self) -> &[&str] {
static EXTENSIONS: &[&str] = &["gltf", "glb"];
EXTENSIONS
}
}
fn load_gltf(bytes: &[u8], load_context: &mut LoadContext) -> Result<(), GltfError> {
let gltf = gltf::Gltf::from_slice(bytes)?;
let mut world = World::default();
let buffer_data = load_buffers(&gltf, load_context, load_context.path())?;
let world_builder = &mut world.build();
for mesh in gltf.meshes() {
for primitive in mesh.primitives() {
let primitive_label = primitive_label(&mesh, &primitive);
if !load_context.has_labeled_asset(&primitive_label) {
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);
if let Some(vertex_attribute) = reader
.read_positions()
.map(|v| VertexAttribute::position(v.collect()))
{
mesh.attributes.push(vertex_attribute);
}
if let Some(vertex_attribute) = reader
.read_normals()
.map(|v| VertexAttribute::normal(v.collect()))
{
mesh.attributes.push(vertex_attribute);
}
if let Some(vertex_attribute) = reader
.read_tex_coords(0)
.map(|v| VertexAttribute::uv(v.into_f32().collect()))
{
mesh.attributes.push(vertex_attribute);
}
if let Some(indices) = reader.read_indices() {
mesh.indices = Some(Indices::U32(indices.into_u32().collect()));
};
load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh));
};
}
}
for texture in gltf.textures() {
if let gltf::image::Source::View { view, mime_type } = texture.source().source() {
let start = view.offset() as usize;
let end = (view.offset() + view.length()) as usize;
let buffer = &buffer_data[view.buffer().index()][start..end];
let format = match mime_type {
"image/png" => Ok(ImageFormat::Png),
"image/jpeg" => Ok(ImageFormat::Jpeg),
_ => Err(GltfError::InvalidImageMimeType(mime_type.to_string())),
}?;
let image = image::load_from_memory_with_format(buffer, format)?;
let size = image.dimensions();
let image = image
.as_rgba8()
.ok_or(GltfError::ImageRgb8ConversionFailure)?;
let texture_label = texture_label(&texture);
load_context.set_labeled_asset(
&texture_label,
LoadedAsset::new(Texture {
data: image.clone().into_vec(),
size: bevy_math::f32::vec2(size.0 as f32, size.1 as f32),
format: TextureFormat::Rgba8Unorm,
}),
);
}
}
for material in gltf.materials() {
let material_label = material_label(&material);
let pbr = material.pbr_metallic_roughness();
let mut dependencies = Vec::new();
let texture_handle = if let Some(info) = pbr.base_color_texture() {
match info.texture().source().source() {
gltf::image::Source::View { .. } => {
let label = texture_label(&info.texture());
let path = AssetPath::new_ref(load_context.path(), Some(&label));
Some(load_context.get_handle(path))
}
gltf::image::Source::Uri { uri, .. } => {
let parent = load_context.path().parent().unwrap();
let image_path = parent.join(uri);
let asset_path = AssetPath::new(image_path, None);
let handle = load_context.get_handle(asset_path.clone());
dependencies.push(asset_path);
Some(handle)
}
}
} else {
None
};
let color = pbr.base_color_factor();
load_context.set_labeled_asset(
&material_label,
LoadedAsset::new(StandardMaterial {
albedo: Color::rgba(color[0], color[1], color[2], color[3]),
albedo_texture: texture_handle,
..Default::default()
})
.with_dependencies(dependencies),
)
}
for scene in gltf.scenes() {
let mut err = None;
world_builder
.spawn((Transform::default(), GlobalTransform::default()))
.with_children(|parent| {
for node in scene.nodes() {
let result = load_node(&node, parent, load_context, &buffer_data);
if result.is_err() {
err = Some(result);
return;
}
}
});
if let Some(Err(err)) = err {
return Err(err);
}
}
load_context.set_default_asset(LoadedAsset::new(Scene::new(world)));
Ok(())
}
fn load_node(
node: &gltf::Node,
world_builder: &mut WorldChildBuilder,
load_context: &mut LoadContext,
buffer_data: &[Vec<u8>],
) -> Result<(), GltfError> {
let transform = node.transform();
let mut gltf_error = None;
world_builder
.spawn((
Transform::from_matrix(Mat4::from_cols_array_2d(&transform.matrix())),
GlobalTransform::default(),
))
.with_children(|parent| {
if let Some(mesh) = node.mesh() {
for primitive in mesh.primitives() {
let primitive_label = primitive_label(&mesh, &primitive);
let mesh_asset_path =
AssetPath::new_ref(load_context.path(), Some(&primitive_label));
let material = primitive.material();
let material_label = material_label(&material);
let material_asset_path =
AssetPath::new_ref(load_context.path(), Some(&material_label));
parent.spawn(PbrComponents {
mesh: load_context.get_handle(mesh_asset_path),
material: load_context.get_handle(material_asset_path),
..Default::default()
});
}
}
if parent.current_entity().is_none() {
return;
}
parent.with_children(|parent| {
for child in node.children() {
if let Err(err) = load_node(&child, parent, load_context, buffer_data) {
gltf_error = Some(err);
return;
}
}
});
});
if let Some(err) = gltf_error {
Err(err)
} else {
Ok(())
}
}
fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
format!("Mesh{}/Primitive{}", mesh.index(), primitive.index())
}
fn material_label(material: &gltf::Material) -> String {
if let Some(index) = material.index() {
format!("Material{}", index)
} else {
"MaterialDefault".to_string()
}
}
fn texture_label(texture: &gltf::Texture) -> String {
format!("Texture{}", texture.index())
}
fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
@ -55,70 +267,17 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
}
}
// TODO: this should return a scene
pub fn load_gltf(asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh, GltfError> {
let gltf = gltf::Gltf::from_slice(&bytes)?;
let buffer_data = load_buffers(&gltf, asset_path)?;
for scene in gltf.scenes() {
if let Some(node) = scene.nodes().next() {
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() {
if let Some(primitive) = mesh.primitives().next() {
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);
if let Some(vertex_attribute) = reader
.read_positions()
.map(|v| VertexAttribute::position(v.collect()))
{
mesh.attributes.push(vertex_attribute);
}
if let Some(vertex_attribute) = reader
.read_normals()
.map(|v| VertexAttribute::normal(v.collect()))
{
mesh.attributes.push(vertex_attribute);
}
if let Some(vertex_attribute) = reader
.read_tex_coords(0)
.map(|v| VertexAttribute::uv(v.into_f32().collect()))
{
mesh.attributes.push(vertex_attribute);
}
if let Some(indices) = reader.read_indices() {
mesh.indices = Some(Indices::U32(indices.into_u32().collect()));
}
return Ok(mesh);
}
}
if let Some(child) = node.children().next() {
return Ok(load_node(buffer_data, &child, depth + 1)?);
}
panic!("failed to find mesh")
}
fn load_buffers(gltf: &gltf::Gltf, asset_path: &Path) -> Result<Vec<Vec<u8>>, GltfError> {
fn load_buffers(
gltf: &gltf::Gltf,
load_context: &LoadContext,
asset_path: &Path,
) -> Result<Vec<Vec<u8>>, GltfError> {
const OCTET_STREAM_URI: &str = "data:application/octet-stream;base64,";
let mut buffer_data = Vec::new();
for buffer in gltf.buffers() {
match buffer.source() {
Source::Uri(uri) => {
gltf::buffer::Source::Uri(uri) => {
if uri.starts_with("data:") {
if uri.starts_with(OCTET_STREAM_URI) {
buffer_data.push(base64::decode(&uri[OCTET_STREAM_URI.len()..])?);
@ -126,12 +285,13 @@ fn load_buffers(gltf: &gltf::Gltf, asset_path: &Path) -> Result<Vec<Vec<u8>>, Gl
return Err(GltfError::BufferFormatUnsupported);
}
} else {
// TODO: Remove this and add dep
let buffer_path = asset_path.parent().unwrap().join(uri);
let buffer_bytes = fs::read(buffer_path)?;
let buffer_bytes = load_context.read_asset_bytes(buffer_path)?;
buffer_data.push(buffer_bytes);
}
}
Source::Bin => {
gltf::buffer::Source::Bin => {
if let Some(blob) = gltf.blob.as_deref() {
buffer_data.push(blob.into());
} else {

View file

@ -13,9 +13,9 @@ pub mod prelude {
}
use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_asset::{AddAsset, Assets, Handle};
use bevy_ecs::IntoQuerySystem;
use bevy_render::{render_graph::RenderGraph, shader};
use bevy_render::{prelude::Color, render_graph::RenderGraph, shader};
use bevy_type_registry::RegisterType;
use light::Light;
use material::StandardMaterial;
@ -36,5 +36,19 @@ impl Plugin for PbrPlugin {
let resources = app.resources();
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
add_pbr_graph(&mut render_graph, resources);
// add default StandardMaterial
let mut materials = app
.resources()
.get_mut::<Assets<StandardMaterial>>()
.unwrap();
materials.set_untracked(
Handle::<StandardMaterial>::default(),
StandardMaterial {
albedo: Color::PINK,
shaded: false,
albedo_texture: None,
},
);
}
}

View file

@ -1,9 +1,10 @@
use bevy_asset::{self, Handle};
use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture};
use bevy_type_registry::TypeUuid;
/// A material with "standard" properties used in PBR lighting
#[derive(Debug, RenderResources, ShaderDefs)]
#[allow(clippy::manual_non_exhaustive)]
#[derive(Debug, RenderResources, ShaderDefs, TypeUuid)]
#[uuid = "dace545e-4bc6-4595-a79d-c224fc694975"]
pub struct StandardMaterial {
pub albedo: Color,
#[shader_def]
@ -11,12 +12,6 @@ pub struct StandardMaterial {
#[render_resources(ignore)]
#[shader_def]
pub shaded: bool,
// this is a manual implementation of the non exhaustive pattern,
// especially made to allow ..Default::default()
#[render_resources(ignore)]
#[doc(hidden)]
pub __non_exhaustive: (),
}
impl Default for StandardMaterial {
@ -25,7 +20,6 @@ impl Default for StandardMaterial {
albedo: Color::rgb(1.0, 1.0, 1.0),
albedo_texture: None,
shaded: true,
__non_exhaustive: (),
}
}
}

View file

@ -8,9 +8,10 @@ use bevy_render::{
shader::{Shader, ShaderStage, ShaderStages},
texture::TextureFormat,
};
use bevy_type_registry::TypeUuid;
pub const FORWARD_PIPELINE_HANDLE: Handle<PipelineDescriptor> =
Handle::from_u128(131483623140127713893804825450360211204);
Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 13148362314012771389);
pub(crate) fn build_forward_pipeline(shaders: &mut Assets<Shader>) -> PipelineDescriptor {
PipelineDescriptor {

View file

@ -38,7 +38,7 @@ pub(crate) fn add_pbr_graph(graph: &mut RenderGraph, resources: &Resources) {
graph.add_system_node(node::LIGHTS, LightsNode::new(10));
let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap();
let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
pipelines.set(
pipelines.set_untracked(
FORWARD_PIPELINE_HANDLE,
build_forward_pipeline(&mut shaders),
);

View file

@ -1,36 +0,0 @@
use super::{BatchKey, Key};
#[derive(Debug, Eq, PartialEq)]
pub struct Batch<TKey, TValue, TData>
where
TKey: Key,
{
pub batch_key: BatchKey<TKey>,
pub values: Vec<TValue>,
pub data: TData,
}
impl<TKey, TValue, TData> Batch<TKey, TValue, TData>
where
TKey: Key,
{
pub fn new(batch_key: BatchKey<TKey>, data: TData) -> Self {
Batch {
data,
values: Vec::new(),
batch_key,
}
}
pub fn add(&mut self, value: TValue) {
self.values.push(value);
}
pub fn iter(&self) -> impl Iterator<Item = &TValue> {
self.values.iter()
}
pub fn get_key(&self, index: usize) -> Option<&TKey> {
self.batch_key.0.get(index)
}
}

View file

@ -1,292 +0,0 @@
use super::Batch;
use bevy_utils::HashMap;
use smallvec::{smallvec, SmallVec};
use std::{borrow::Cow, fmt, hash::Hash};
// TODO: add sorting by primary / secondary handle to reduce rebinds of data
// TValue: entityid
// TKey: handleuntyped
pub trait Key: Clone + Eq + Hash + 'static {}
impl<T: Clone + Eq + Hash + 'static> Key for T {}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct BatchKey<TKey: Key>(pub Cow<'static, SmallVec<[TKey; 2]>>);
impl<TKey: Key> BatchKey<TKey> {
pub fn key1(key: TKey) -> Self {
BatchKey(Cow::Owned(smallvec![key]))
}
pub fn key2(key1: TKey, key2: TKey) -> Self {
BatchKey(Cow::Owned(smallvec![key1, key2]))
}
pub fn key3(key1: TKey, key2: TKey, key3: TKey) -> Self {
BatchKey(Cow::Owned(smallvec![key1, key2, key3]))
}
}
#[derive(Debug)]
pub struct BatcherKeyState<TKey: Key> {
batch_key: Option<BatchKey<TKey>>,
keys: SmallVec<[Option<TKey>; 2]>,
}
impl<TKey: Key> BatcherKeyState<TKey> {
pub fn new(size: usize) -> Self {
BatcherKeyState {
keys: smallvec![None; size],
batch_key: None,
}
}
pub fn set(&mut self, index: usize, key: TKey) {
self.keys[index] = Some(key);
}
pub fn finish(&mut self) -> Option<BatchKey<TKey>> {
let finished = self.keys.iter().filter(|x| x.is_some()).count() == self.keys.len();
if finished {
let batch_key = BatchKey(Cow::Owned(
self.keys
.drain(..)
.map(|k| k.unwrap())
.collect::<SmallVec<[TKey; 2]>>(),
));
self.batch_key = Some(batch_key);
self.batch_key.clone()
} else {
None
}
}
}
/// An unordered batcher intended to support an arbitrary number of keys of the same type (but with some distinguishing factor)
/// NOTE: this may or may not be useful for anything. when paired with a higher-level "BatcherSet" it would allow updating batches
// per-key (ex: material, mesh) with no global knowledge of the number of batch types (ex: (Mesh), (Material, Mesh)) that key belongs
// to. The downside is that it is completely unordered, so it probably isn't useful for front->back or back->front rendering. But
// _maybe_ for gpu instancing?
pub struct Batcher<TKey, TValue, TData>
where
TKey: Key,
{
pub batches: HashMap<BatchKey<TKey>, Batch<TKey, TValue, TData>>,
pub is_index: Vec<fn(&TKey) -> bool>,
pub key_states: HashMap<TValue, BatcherKeyState<TKey>>,
pub key_count: usize,
}
impl<TKey: Key, TValue, TData> fmt::Debug for Batcher<TKey, TValue, TData>
where
TKey: Key + fmt::Debug,
TValue: fmt::Debug,
TData: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let is_index = self
.is_index
.iter()
.map(|f| f as *const for<'r> fn(&'r TKey) -> bool)
.collect::<Vec<_>>();
f.debug_struct("Batcher")
.field("batches", &self.batches)
.field("is_index", &is_index)
.field("key_states", &self.key_states)
.field("key_count", &self.key_count)
.finish()
}
}
impl<TKey, TValue, TData> Batcher<TKey, TValue, TData>
where
TKey: Key,
TValue: Clone + Eq + Hash,
TData: Default,
{
pub fn new(is_index: Vec<fn(&TKey) -> bool>) -> Self {
Batcher {
batches: HashMap::default(),
key_states: HashMap::default(),
key_count: is_index.len(),
is_index,
}
}
pub fn get_batch(&self, batch_key: &BatchKey<TKey>) -> Option<&Batch<TKey, TValue, TData>> {
self.batches.get(batch_key)
}
pub fn get_batch_mut(
&mut self,
batch_key: &BatchKey<TKey>,
) -> Option<&mut Batch<TKey, TValue, TData>> {
self.batches.get_mut(batch_key)
}
pub fn add(&mut self, key: TKey, value: TValue) -> bool {
let batch_key = {
let key_count = self.key_count;
let key_state = self
.key_states
.entry(value.clone())
.or_insert_with(|| BatcherKeyState::new(key_count));
// if all key states are set, the value is already in the batch
if key_state.batch_key.is_some() {
// TODO: if weights are ever added, make sure to get the batch and set the weight here
return true;
}
let key_index = self
.is_index
.iter()
.enumerate()
.find(|(_i, is_index)| is_index(&key))
.map(|(i, _)| i);
if let Some(key_index) = key_index {
key_state.set(key_index, key);
key_state.finish()
} else {
return false;
}
};
if let Some(batch_key) = batch_key {
let batch = self
.batches
.entry(batch_key.clone())
.or_insert_with(|| Batch::new(batch_key, TData::default()));
batch.add(value);
}
true
}
pub fn iter(&self) -> impl Iterator<Item = &Batch<TKey, TValue, TData>> {
self.batches.values()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Batch<TKey, TValue, TData>> {
self.batches.values_mut()
}
}
#[cfg(test)]
mod tests {
use super::{Batch, BatchKey, Batcher};
use bevy_asset::{Handle, HandleUntyped};
#[derive(Debug, Eq, PartialEq)]
struct A;
#[derive(Debug, Eq, PartialEq)]
struct B;
#[derive(Debug, Eq, PartialEq)]
struct C;
#[derive(Debug, Eq, PartialEq, Default)]
struct Data;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
struct Entity(usize);
#[test]
fn test_batcher_2() {
let mut batcher: Batcher<HandleUntyped, Entity, Data> = Batcher::new(vec![
HandleUntyped::is_handle::<A>,
HandleUntyped::is_handle::<B>,
]);
let e1 = Entity(1);
let e2 = Entity(2);
let e3 = Entity(3);
let a1: HandleUntyped = Handle::<A>::new().into();
let b1: HandleUntyped = Handle::<B>::new().into();
let c1: HandleUntyped = Handle::<C>::new().into();
let a2: HandleUntyped = Handle::<A>::new().into();
let b2: HandleUntyped = Handle::<B>::new().into();
let a1_b1 = BatchKey::key2(a1, b1);
let a2_b2 = BatchKey::key2(a2, b2);
assert_eq!(
batcher.get_batch(&a1_b1),
None,
"a1_b1 batch should not exist yet"
);
batcher.add(a1, e1);
assert_eq!(
batcher.get_batch(&a1_b1),
None,
"a1_b1 batch should not exist yet"
);
batcher.add(b1, e1);
let a1_b1_batch = Batch {
batch_key: a1_b1.clone(),
values: vec![e1],
data: Data,
};
assert_eq!(
batcher.get_batch(&a1_b1),
Some(&a1_b1_batch),
"a1_b1 batch should exist"
);
assert_eq!(
batcher.get_batch(&a2_b2),
None,
"a2_b2 batch should not exist yet"
);
batcher.add(a2, e2);
assert_eq!(
batcher.get_batch(&a2_b2),
None,
"a2_b2 batch should not exist yet"
);
batcher.add(b2, e2);
let expected_batch = Batch {
batch_key: a2_b2.clone(),
values: vec![e2],
data: Data,
};
assert_eq!(
batcher.get_batch(&a2_b2),
Some(&expected_batch),
"a2_b2 batch should have e2"
);
batcher.add(a2, e3);
batcher.add(b2, e3);
batcher.add(c1, e3); // this should be ignored
let a2_b2_batch = Batch {
batch_key: a2_b2.clone(),
values: vec![e2, e3],
data: Data,
};
assert_eq!(
batcher.get_batch(&a2_b2),
Some(&a2_b2_batch),
"a2_b2 batch should have e2 and e3"
);
let mut found_a1_b1 = false;
let mut found_a2_b2 = false;
for batch in batcher.iter() {
if batch == &a1_b1_batch {
found_a1_b1 = true;
} else if batch == &a2_b2_batch {
found_a2_b2 = true;
}
}
assert!(found_a1_b1 && found_a2_b2);
assert_eq!(batcher.iter().count(), 2);
}
}

View file

@ -1,10 +0,0 @@
// mod asset_batcher;
// mod asset_batcher2;
#[allow(clippy::module_inception)]
mod batch;
mod batcher;
// pub use asset_batcher::*;
// pub use asset_batcher2::*;
pub use batch::*;
pub use batcher::*;

View file

@ -28,6 +28,7 @@ impl Color {
pub const BLUE: Color = Color::rgb_linear(0.0, 0.0, 1.0);
pub const GREEN: Color = Color::rgb_linear(0.0, 1.0, 0.0);
pub const NONE: Color = Color::rgba_linear(0.0, 0.0, 0.0, 0.0);
pub const PINK: Color = Color::rgb_linear(1.0, 0.08, 0.58);
pub const RED: Color = Color::rgb_linear(1.0, 0.0, 0.0);
pub const WHITE: Color = Color::rgb_linear(1.0, 1.0, 1.0);
@ -259,11 +260,18 @@ impl From<Vec4> for Color {
}
}
impl From<[f32; 4]> for Color {
fn from(value: [f32; 4]) -> Self {
Color::rgba(value[0], value[1], value[2], value[3])
}
}
impl Into<[f32; 4]> for Color {
fn into(self) -> [f32; 4] {
[self.red, self.green, self.blue, self.alpha]
}
}
impl Mul<f32> for Color {
type Output = Color;

View file

@ -73,8 +73,10 @@ impl Draw {
self.render_commands.clear();
}
pub fn set_pipeline(&mut self, pipeline: Handle<PipelineDescriptor>) {
self.render_command(RenderCommand::SetPipeline { pipeline });
pub fn set_pipeline(&mut self, pipeline: &Handle<PipelineDescriptor>) {
self.render_command(RenderCommand::SetPipeline {
pipeline: pipeline.clone_weak(),
});
}
pub fn set_vertex_buffer(&mut self, slot: u32, buffer: BufferId, offset: u64) {
@ -143,7 +145,7 @@ impl<'a> UnsafeClone for DrawContext<'a> {
render_resource_context: self.render_resource_context.unsafe_clone(),
vertex_buffer_descriptors: self.vertex_buffer_descriptors.unsafe_clone(),
shared_buffers: self.shared_buffers.unsafe_clone(),
current_pipeline: self.current_pipeline,
current_pipeline: self.current_pipeline.clone(),
}
}
}
@ -252,7 +254,7 @@ impl<'a> DrawContext<'a> {
pub fn set_pipeline(
&mut self,
draw: &mut Draw,
pipeline_handle: Handle<PipelineDescriptor>,
pipeline_handle: &Handle<PipelineDescriptor>,
specialization: &PipelineSpecialization,
) -> Result<(), DrawError> {
let specialized_pipeline = if let Some(specialized_pipeline) = self
@ -271,14 +273,15 @@ impl<'a> DrawContext<'a> {
)
};
draw.set_pipeline(specialized_pipeline);
self.current_pipeline = Some(specialized_pipeline);
draw.set_pipeline(&specialized_pipeline);
self.current_pipeline = Some(specialized_pipeline.clone_weak());
Ok(())
}
pub fn get_pipeline_descriptor(&self) -> Result<&PipelineDescriptor, DrawError> {
self.current_pipeline
.and_then(|handle| self.pipelines.get(&handle))
.as_ref()
.and_then(|handle| self.pipelines.get(handle))
.ok_or(DrawError::NoPipelineSet)
}
@ -295,10 +298,13 @@ impl<'a> DrawContext<'a> {
draw: &mut Draw,
render_resource_bindings: &mut [&mut RenderResourceBindings],
) -> Result<(), DrawError> {
let pipeline = self.current_pipeline.ok_or(DrawError::NoPipelineSet)?;
let pipeline = self
.current_pipeline
.as_ref()
.ok_or(DrawError::NoPipelineSet)?;
let pipeline_descriptor = self
.pipelines
.get(&pipeline)
.get(pipeline)
.ok_or(DrawError::NonExistentPipeline)?;
let layout = pipeline_descriptor
.get_layout()
@ -325,10 +331,13 @@ impl<'a> DrawContext<'a> {
index: u32,
bind_group: &BindGroup,
) -> Result<(), DrawError> {
let pipeline = self.current_pipeline.ok_or(DrawError::NoPipelineSet)?;
let pipeline = self
.current_pipeline
.as_ref()
.ok_or(DrawError::NoPipelineSet)?;
let pipeline_descriptor = self
.pipelines
.get(&pipeline)
.get(pipeline)
.ok_or(DrawError::NonExistentPipeline)?;
let layout = pipeline_descriptor
.get_layout()
@ -344,10 +353,13 @@ impl<'a> DrawContext<'a> {
draw: &mut Draw,
render_resource_bindings: &[&RenderResourceBindings],
) -> Result<(), DrawError> {
let pipeline = self.current_pipeline.ok_or(DrawError::NoPipelineSet)?;
let pipeline = self
.current_pipeline
.as_ref()
.ok_or(DrawError::NoPipelineSet)?;
let pipeline_descriptor = self
.pipelines
.get(&pipeline)
.get(pipeline)
.ok_or(DrawError::NonExistentPipeline)?;
let layout = pipeline_descriptor
.get_layout()

View file

@ -1,4 +1,3 @@
pub mod batch;
pub mod camera;
pub mod color;
pub mod colorspace;
@ -12,6 +11,7 @@ pub mod renderer;
pub mod shader;
pub mod texture;
use bevy_type_registry::RegisterType;
pub use once_cell;
pub mod prelude {
@ -33,7 +33,6 @@ use base::{MainPass, Msaa};
use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_ecs::{IntoQuerySystem, IntoThreadLocalSystem};
use bevy_type_registry::RegisterType;
use camera::{
ActiveCameras, Camera, OrthographicProjection, PerspectiveProjection, VisibleEntities,
};
@ -83,11 +82,11 @@ impl Plugin for RenderPlugin {
fn build(&self, app: &mut AppBuilder) {
#[cfg(feature = "png")]
{
app.add_asset_loader::<Texture, ImageTextureLoader>();
app.init_asset_loader::<ImageTextureLoader>();
}
#[cfg(feature = "hdr")]
{
app.add_asset_loader::<Texture, HdrTextureLoader>();
app.init_asset_loader::<HdrTextureLoader>();
}
if app.resources().get::<ClearColor>().is_none() {

View file

@ -11,6 +11,7 @@ use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_core::AsBytes;
use bevy_ecs::{Local, Query, Res, ResMut};
use bevy_math::*;
use bevy_type_registry::TypeUuid;
use bevy_utils::HashSet;
use std::borrow::Cow;
use thiserror::Error;
@ -112,7 +113,8 @@ pub enum Indices {
U32(Vec<u32>),
}
#[derive(Debug)]
#[derive(Debug, TypeUuid)]
#[uuid = "8ecbac0f-f545-4473-ad43-e1f4243af51e"]
pub struct Mesh {
pub primitive_topology: PrimitiveTopology,
pub attributes: Vec<VertexAttribute>,
@ -476,10 +478,10 @@ pub mod shape {
fn remove_current_mesh_resources(
render_resource_context: &dyn RenderResourceContext,
handle: Handle<Mesh>,
handle: &Handle<Mesh>,
) {
if let Some(RenderResourceId::Buffer(buffer)) =
render_resource_context.get_asset_resource(handle, VERTEX_BUFFER_ASSET_INDEX)
render_resource_context.get_asset_resource(&handle, VERTEX_BUFFER_ASSET_INDEX)
{
render_resource_context.remove_buffer(buffer);
render_resource_context.remove_asset_resource(handle, VERTEX_BUFFER_ASSET_INDEX);
@ -520,15 +522,15 @@ pub fn mesh_resource_provider_system(
let render_resource_context = &**render_resource_context;
for event in state.mesh_event_reader.iter(&mesh_events) {
match event {
AssetEvent::Created { handle } => {
changed_meshes.insert(*handle);
AssetEvent::Created { ref handle } => {
changed_meshes.insert(handle.clone_weak());
}
AssetEvent::Modified { handle } => {
changed_meshes.insert(*handle);
remove_current_mesh_resources(render_resource_context, *handle);
AssetEvent::Modified { ref handle } => {
changed_meshes.insert(handle.clone_weak());
remove_current_mesh_resources(render_resource_context, handle);
}
AssetEvent::Removed { handle } => {
remove_current_mesh_resources(render_resource_context, *handle);
AssetEvent::Removed { ref handle } => {
remove_current_mesh_resources(render_resource_context, handle);
// if mesh was modified and removed in the same update, ignore the modification
// events are ordered so future modification events are ok
changed_meshes.remove(handle);
@ -560,12 +562,12 @@ pub fn mesh_resource_provider_system(
);
render_resource_context.set_asset_resource(
*changed_mesh_handle,
changed_mesh_handle,
RenderResourceId::Buffer(vertex_buffer),
VERTEX_BUFFER_ASSET_INDEX,
);
render_resource_context.set_asset_resource(
*changed_mesh_handle,
changed_mesh_handle,
RenderResourceId::Buffer(index_buffer),
INDEX_BUFFER_ASSET_INDEX,
);
@ -574,20 +576,20 @@ pub fn mesh_resource_provider_system(
// TODO: remove this once batches are pipeline specific and deprecate assigned_meshes draw target
for (handle, mut render_pipelines) in &mut query.iter() {
if let Some(mesh) = meshes.get(&handle) {
if let Some(mesh) = meshes.get(handle) {
for render_pipeline in render_pipelines.pipelines.iter_mut() {
render_pipeline.specialization.primitive_topology = mesh.primitive_topology;
}
}
if let Some(RenderResourceId::Buffer(vertex_buffer)) =
render_resource_context.get_asset_resource(*handle, VERTEX_BUFFER_ASSET_INDEX)
render_resource_context.get_asset_resource(handle, VERTEX_BUFFER_ASSET_INDEX)
{
render_pipelines.bindings.set_vertex_buffer(
"Vertex",
vertex_buffer,
render_resource_context
.get_asset_resource(*handle, INDEX_BUFFER_ASSET_INDEX)
.get_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX)
.and_then(|r| {
if let RenderResourceId::Buffer(buffer) = r {
Some(buffer)

View file

@ -9,7 +9,7 @@ pub trait RenderPass {
fn get_render_context(&self) -> &dyn RenderContext;
fn set_index_buffer(&mut self, buffer: BufferId, offset: u64);
fn set_vertex_buffer(&mut self, start_slot: u32, buffer: BufferId, offset: u64);
fn set_pipeline(&mut self, pipeline_handle: Handle<PipelineDescriptor>);
fn set_pipeline(&mut self, pipeline_handle: &Handle<PipelineDescriptor>);
fn set_viewport(&mut self, x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32);
fn set_stencil_reference(&mut self, reference: u32);
fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>);

View file

@ -11,8 +11,10 @@ use crate::{
texture::TextureFormat,
};
use bevy_asset::Assets;
use bevy_type_registry::TypeUuid;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, TypeUuid)]
#[uuid = "ebfc1d11-a2a4-44cb-8f12-c49cc631146c"]
pub struct PipelineDescriptor {
pub name: Option<String>,
pub layout: Option<PipelineLayout>,
@ -137,7 +139,7 @@ impl PipelineDescriptor {
.shader_stages
.fragment
.as_ref()
.map(|handle| shaders.get(&handle).unwrap());
.map(|handle| shaders.get(handle).unwrap());
let mut layouts = vec![vertex_spirv.reflect_layout(bevy_conventions).unwrap()];
if let Some(ref fragment_spirv) = fragment_spirv {

View file

@ -77,14 +77,14 @@ impl PipelineCompiler {
) -> Handle<Shader> {
let specialized_shaders = self
.specialized_shaders
.entry(*shader_handle)
.entry(shader_handle.clone_weak())
.or_insert_with(Vec::new);
let shader = shaders.get(shader_handle).unwrap();
// don't produce new shader if the input source is already spirv
if let ShaderSource::Spirv(_) = shader.source {
return *shader_handle;
return shader_handle.clone_weak();
}
if let Some(specialized_shader) =
@ -95,7 +95,7 @@ impl PipelineCompiler {
})
{
// if shader has already been compiled with current configuration, use existing shader
specialized_shader.shader
specialized_shader.shader.clone_weak()
} else {
// if no shader exists with the current configuration, create new shader and compile
let shader_def_vec = shader_specialization
@ -105,21 +105,22 @@ impl PipelineCompiler {
.collect::<Vec<String>>();
let compiled_shader = shader.get_spirv_shader(Some(&shader_def_vec));
let specialized_handle = shaders.add(compiled_shader);
let weak_specialized_handle = specialized_handle.clone_weak();
specialized_shaders.push(SpecializedShader {
shader: specialized_handle,
specialization: shader_specialization.clone(),
});
specialized_handle
weak_specialized_handle
}
}
pub fn get_specialized_pipeline(
&self,
pipeline: Handle<PipelineDescriptor>,
pipeline: &Handle<PipelineDescriptor>,
specialization: &PipelineSpecialization,
) -> Option<Handle<PipelineDescriptor>> {
self.specialized_pipelines
.get(&pipeline)
.get(pipeline)
.and_then(|specialized_pipelines| {
specialized_pipelines
.iter()
@ -127,7 +128,7 @@ impl PipelineCompiler {
&current_specialized_pipeline.specialization == specialization
})
})
.map(|specialized_pipeline| specialized_pipeline.pipeline)
.map(|specialized_pipeline| specialized_pipeline.pipeline.clone_weak())
}
pub fn compile_pipeline(
@ -135,11 +136,11 @@ impl PipelineCompiler {
render_resource_context: &dyn RenderResourceContext,
pipelines: &mut Assets<PipelineDescriptor>,
shaders: &mut Assets<Shader>,
source_pipeline: Handle<PipelineDescriptor>,
source_pipeline: &Handle<PipelineDescriptor>,
vertex_buffer_descriptors: &VertexBufferDescriptors,
pipeline_specialization: &PipelineSpecialization,
) -> Handle<PipelineDescriptor> {
let source_descriptor = pipelines.get(&source_pipeline).unwrap();
let source_descriptor = pipelines.get(source_pipeline).unwrap();
let mut specialized_descriptor = source_descriptor.clone();
specialized_descriptor.shader_stages.vertex = self.compile_shader(
shaders,
@ -171,21 +172,22 @@ impl PipelineCompiler {
let specialized_pipeline_handle = pipelines.add(specialized_descriptor);
render_resource_context.create_render_pipeline(
specialized_pipeline_handle,
specialized_pipeline_handle.clone_weak(),
pipelines.get(&specialized_pipeline_handle).unwrap(),
&shaders,
);
let specialized_pipelines = self
.specialized_pipelines
.entry(source_pipeline)
.entry(source_pipeline.clone_weak())
.or_insert_with(Vec::new);
let weak_specialized_pipeline_handle = specialized_pipeline_handle.clone_weak();
specialized_pipelines.push(SpecializedPipeline {
pipeline: specialized_pipeline_handle,
specialization: pipeline_specialization.clone(),
});
specialized_pipeline_handle
weak_specialized_pipeline_handle
}
pub fn iter_compiled_pipelines(

View file

@ -56,7 +56,7 @@ impl RenderPipelines {
RenderPipelines {
pipelines: handles
.into_iter()
.map(|pipeline| RenderPipeline::new(*pipeline))
.map(|pipeline| RenderPipeline::new(pipeline.clone_weak()))
.collect::<Vec<RenderPipeline>>(),
..Default::default()
}
@ -84,7 +84,13 @@ pub fn draw_render_pipelines_system(
continue;
}
let mesh = meshes.get(mesh_handle).unwrap();
// don't render if the mesh isn't loaded yet
let mesh = if let Some(mesh) = meshes.get(mesh_handle) {
mesh
} else {
continue;
};
let (index_range, index_format) = match mesh.indices.as_ref() {
Some(Indices::U32(indices)) => (Some(0..indices.len() as u32), IndexFormat::Uint32),
Some(Indices::U16(indices)) => (Some(0..indices.len() as u32), IndexFormat::Uint16),
@ -101,7 +107,7 @@ pub fn draw_render_pipelines_system(
draw_context
.set_pipeline(
&mut draw,
render_pipeline.pipeline,
&render_pipeline.pipeline,
&render_pipeline.specialization,
)
.unwrap();

View file

@ -246,9 +246,9 @@ where
match render_command {
RenderCommand::SetPipeline { pipeline } => {
// TODO: Filter pipelines
render_pass.set_pipeline(*pipeline);
render_pass.set_pipeline(pipeline);
let descriptor = pipelines.get(pipeline).unwrap();
draw_state.set_pipeline(*pipeline, descriptor);
draw_state.set_pipeline(pipeline, descriptor);
// try to set current camera bind group
let layout = descriptor.get_layout().unwrap();
@ -303,7 +303,7 @@ where
bind_group,
dynamic_uniform_indices,
} => {
let pipeline = pipelines.get(&draw_state.pipeline.unwrap()).unwrap();
let pipeline = pipelines.get(draw_state.pipeline.as_ref().unwrap()).unwrap();
let layout = pipeline.get_layout().unwrap();
let bind_group_descriptor = layout.get_bind_group(*index).unwrap();
render_pass.set_bind_group(
@ -358,14 +358,14 @@ impl DrawState {
pub fn set_pipeline(
&mut self,
handle: Handle<PipelineDescriptor>,
handle: &Handle<PipelineDescriptor>,
descriptor: &PipelineDescriptor,
) {
self.bind_groups.clear();
self.vertex_buffers.clear();
self.index_buffer = None;
self.pipeline = Some(handle);
self.pipeline = Some(handle.clone_weak());
let layout = descriptor.get_layout().unwrap();
self.bind_groups.resize(layout.bind_groups.len(), None);
self.vertex_buffers

View file

@ -9,7 +9,7 @@ use crate::{
texture,
};
use bevy_asset::{Assets, Handle};
use bevy_asset::{Asset, Assets, Handle, HandleId};
use bevy_ecs::{
Commands, Entity, IntoQuerySystem, Local, Query, Res, ResMut, Resources, System, World,
};
@ -547,7 +547,7 @@ const EXPECT_ASSET_MESSAGE: &str = "Only assets that exist should be in the modi
impl<T> SystemNode for AssetRenderResourcesNode<T>
where
T: renderer::RenderResources,
T: renderer::RenderResources + Asset,
{
fn get_system(&self, commands: &mut Commands) -> Box<dyn System> {
let system = asset_render_resources_node_system::<T>.system();
@ -555,7 +555,7 @@ where
system.id(),
RenderResourcesNodeState {
command_queue: self.command_queue.clone(),
uniform_buffer_arrays: UniformBufferArrays::<Handle<T>, T>::default(),
uniform_buffer_arrays: UniformBufferArrays::<HandleId, T>::default(),
dynamic_uniforms: self.dynamic_uniforms,
},
);
@ -564,8 +564,8 @@ where
}
}
fn asset_render_resources_node_system<T: RenderResources>(
mut state: Local<RenderResourcesNodeState<Handle<T>, T>>,
fn asset_render_resources_node_system<T: RenderResources + Asset>(
mut state: Local<RenderResourcesNodeState<HandleId, T>>,
assets: Res<Assets<T>>,
mut asset_render_resource_bindings: ResMut<AssetRenderResourceBindings>,
render_resource_context: Res<Box<dyn RenderResourceContext>>,
@ -575,22 +575,20 @@ fn asset_render_resources_node_system<T: RenderResources>(
let uniform_buffer_arrays = &mut state.uniform_buffer_arrays;
let render_resource_context = &**render_resource_context;
let modified_assets = assets
.iter()
.map(|(handle, _)| handle)
.collect::<Vec<Handle<T>>>();
let modified_assets = assets.ids().collect::<Vec<_>>();
uniform_buffer_arrays.begin_update();
// initialize uniform buffer arrays using the first RenderResources
if let Some(first_handle) = modified_assets.get(0) {
let asset = assets.get(first_handle).expect(EXPECT_ASSET_MESSAGE);
let asset = assets.get(*first_handle).expect(EXPECT_ASSET_MESSAGE);
uniform_buffer_arrays.initialize(asset);
}
for asset_handle in modified_assets.iter() {
let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE);
let asset = assets.get(*asset_handle).expect(EXPECT_ASSET_MESSAGE);
uniform_buffer_arrays.prepare_uniform_buffers(*asset_handle, asset);
let mut bindings = asset_render_resource_bindings.get_or_insert_mut(*asset_handle);
let mut bindings =
asset_render_resource_bindings.get_or_insert_mut(&Handle::<T>::weak(*asset_handle));
setup_uniform_texture_resources::<T>(&asset, render_resource_context, &mut bindings);
}
@ -604,9 +602,9 @@ fn asset_render_resources_node_system<T: RenderResources>(
0..state.uniform_buffer_arrays.staging_buffer_size as u64,
&mut |mut staging_buffer, _render_resource_context| {
for asset_handle in modified_assets.iter() {
let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE);
let mut render_resource_bindings =
asset_render_resource_bindings.get_or_insert_mut(*asset_handle);
let asset = assets.get(*asset_handle).expect(EXPECT_ASSET_MESSAGE);
let mut render_resource_bindings = asset_render_resource_bindings
.get_or_insert_mut(&Handle::<T>::weak(*asset_handle));
// TODO: only setup buffer if we haven't seen this handle before
state.uniform_buffer_arrays.write_uniform_buffers(
*asset_handle,
@ -627,9 +625,9 @@ fn asset_render_resources_node_system<T: RenderResources>(
} else {
let mut staging_buffer: [u8; 0] = [];
for asset_handle in modified_assets.iter() {
let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE);
let asset = assets.get(*asset_handle).expect(EXPECT_ASSET_MESSAGE);
let mut render_resource_bindings =
asset_render_resource_bindings.get_or_insert_mut(*asset_handle);
asset_render_resource_bindings.get_or_insert_mut(&Handle::<T>::weak(*asset_handle));
// TODO: only setup buffer if we haven't seen this handle before
state.uniform_buffer_arrays.write_uniform_buffers(
*asset_handle,
@ -646,7 +644,7 @@ fn asset_render_resources_node_system<T: RenderResources>(
if !draw.is_visible {
continue;
}
if let Some(asset_bindings) = asset_render_resource_bindings.get(*asset_handle) {
if let Some(asset_bindings) = asset_render_resource_bindings.get(asset_handle) {
render_pipelines.bindings.extend(asset_bindings);
}
}

View file

@ -31,7 +31,7 @@ impl Node for TextureCopyNode {
for event in self.texture_event_reader.iter(&texture_events) {
match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
if let Some(texture) = textures.get(&handle) {
if let Some(texture) = textures.get(handle) {
let texture_descriptor: TextureDescriptor = texture.into();
let width = texture.size.x() as usize;
let aligned_width = get_aligned(texture.size.x());
@ -57,7 +57,7 @@ impl Node for TextureCopyNode {
let texture_resource = render_context
.resources()
.get_asset_resource(*handle, TEXTURE_ASSET_INDEX)
.get_asset_resource(handle, TEXTURE_ASSET_INDEX)
.unwrap();
render_context.copy_buffer_to_texture(

View file

@ -76,7 +76,7 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
buffer
}
fn create_shader_module(&self, _shader_handle: Handle<Shader>, _shaders: &Assets<Shader>) {}
fn create_shader_module(&self, _shader_handle: &Handle<Shader>, _shaders: &Assets<Shader>) {}
fn remove_buffer(&self, buffer: BufferId) {
self.buffer_info.write().remove(&buffer);
@ -122,7 +122,7 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
) {
}
fn create_shader_module_from_source(&self, _shader_handle: Handle<Shader>, _shader: &Shader) {}
fn create_shader_module_from_source(&self, _shader_handle: &Handle<Shader>, _shader: &Shader) {}
fn remove_asset_resource_untyped(&self, handle: HandleUntyped, index: usize) {
self.asset_resources.write().remove(&(handle, index));

View file

@ -77,7 +77,7 @@ pub trait RenderResource {
fn write_buffer_bytes(&self, buffer: &mut [u8]);
fn buffer_byte_len(&self) -> Option<usize>;
// TODO: consider making these panic by default, but return non-options
fn texture(&self) -> Option<Handle<Texture>>;
fn texture(&self) -> Option<&Handle<Texture>>;
}
pub trait RenderResources: Send + Sync + 'static {
@ -136,7 +136,7 @@ macro_rules! impl_render_resource_bytes {
Some(self.byte_len())
}
fn texture(&self) -> Option<Handle<Texture>> {
fn texture(&self) -> Option<&Handle<Texture>> {
None
}
}
@ -175,7 +175,7 @@ where
Some(self.byte_len())
}
fn texture(&self) -> Option<Handle<Texture>> {
fn texture(&self) -> Option<&Handle<Texture>> {
None
}
}
@ -194,7 +194,7 @@ impl RenderResource for GlobalTransform {
Some(std::mem::size_of::<[f32; 16]>())
}
fn texture(&self) -> Option<Handle<Texture>> {
fn texture(&self) -> Option<&Handle<Texture>> {
None
}
}

View file

@ -3,7 +3,7 @@ use crate::{
pipeline::{BindGroupDescriptor, BindGroupDescriptorId, PipelineDescriptor},
renderer::RenderResourceContext,
};
use bevy_asset::{Handle, HandleUntyped};
use bevy_asset::{Asset, Handle, HandleUntyped};
use bevy_utils::{HashMap, HashSet};
use std::{hash::Hash, ops::Range};
use uuid::Uuid;
@ -259,18 +259,21 @@ pub struct AssetRenderResourceBindings {
}
impl AssetRenderResourceBindings {
pub fn get<T>(&self, handle: Handle<T>) -> Option<&RenderResourceBindings> {
self.bindings.get(&HandleUntyped::from(handle))
pub fn get<T: Asset>(&self, handle: &Handle<T>) -> Option<&RenderResourceBindings> {
self.bindings.get(&handle.clone_weak_untyped())
}
pub fn get_or_insert_mut<T>(&mut self, handle: Handle<T>) -> &mut RenderResourceBindings {
pub fn get_or_insert_mut<T: Asset>(
&mut self,
handle: &Handle<T>,
) -> &mut RenderResourceBindings {
self.bindings
.entry(HandleUntyped::from(handle))
.entry(handle.clone_weak_untyped())
.or_insert_with(RenderResourceBindings::default)
}
pub fn get_mut<T>(&mut self, handle: Handle<T>) -> Option<&mut RenderResourceBindings> {
self.bindings.get_mut(&HandleUntyped::from(handle))
pub fn get_mut<T: Asset>(&mut self, handle: &Handle<T>) -> Option<&mut RenderResourceBindings> {
self.bindings.get_mut(&handle.clone_weak_untyped())
}
}

View file

@ -4,7 +4,7 @@ use crate::{
shader::Shader,
texture::{SamplerDescriptor, TextureDescriptor},
};
use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_asset::{Asset, Assets, Handle, HandleUntyped};
use bevy_window::Window;
use downcast_rs::{impl_downcast, Downcast};
use std::ops::Range;
@ -27,8 +27,8 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
fn map_buffer(&self, id: BufferId);
fn unmap_buffer(&self, id: BufferId);
fn create_buffer_with_data(&self, buffer_info: BufferInfo, data: &[u8]) -> BufferId;
fn create_shader_module(&self, shader_handle: Handle<Shader>, shaders: &Assets<Shader>);
fn create_shader_module_from_source(&self, shader_handle: Handle<Shader>, shader: &Shader);
fn create_shader_module(&self, shader_handle: &Handle<Shader>, shaders: &Assets<Shader>);
fn create_shader_module_from_source(&self, shader_handle: &Handle<Shader>, shader: &Shader);
fn remove_buffer(&self, buffer: BufferId);
fn remove_texture(&self, texture: TextureId);
fn remove_sampler(&self, sampler: SamplerId);
@ -63,25 +63,33 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
}
impl dyn RenderResourceContext {
pub fn set_asset_resource<T>(&self, handle: Handle<T>, resource: RenderResourceId, index: usize)
where
T: 'static,
pub fn set_asset_resource<T>(
&self,
handle: &Handle<T>,
resource: RenderResourceId,
index: usize,
) where
T: Asset,
{
self.set_asset_resource_untyped(handle.into(), resource, index);
self.set_asset_resource_untyped(handle.clone_weak_untyped(), resource, index);
}
pub fn get_asset_resource<T>(&self, handle: Handle<T>, index: usize) -> Option<RenderResourceId>
pub fn get_asset_resource<T>(
&self,
handle: &Handle<T>,
index: usize,
) -> Option<RenderResourceId>
where
T: 'static,
T: Asset,
{
self.get_asset_resource_untyped(handle.into(), index)
self.get_asset_resource_untyped(handle.clone_weak_untyped(), index)
}
pub fn remove_asset_resource<T>(&self, handle: Handle<T>, index: usize)
pub fn remove_asset_resource<T>(&self, handle: &Handle<T>, index: usize)
where
T: 'static,
T: Asset,
{
self.remove_asset_resource_untyped(handle.into(), index);
self.remove_asset_resource_untyped(handle.clone_weak_untyped(), index);
}
}

View file

@ -1,5 +1,6 @@
use super::ShaderLayout;
use bevy_asset::Handle;
use bevy_type_registry::TypeUuid;
use std::marker::Copy;
/// The stage of a shader
@ -98,7 +99,8 @@ impl ShaderSource {
}
/// A shader, as defined by its [ShaderSource] and [ShaderStage]
#[derive(Clone, Debug)]
#[derive(Clone, Debug, TypeUuid)]
#[uuid = "d95bc916-6c55-4de3-9622-37e7b6969fda"]
pub struct Shader {
pub source: ShaderSource,
pub stage: ShaderStage,
@ -164,8 +166,8 @@ impl<'a> Iterator for ShaderStagesIterator<'a> {
fn next(&mut self) -> Option<Self::Item> {
let ret = match self.state {
0 => Some(self.shader_stages.vertex),
1 => self.shader_stages.fragment,
0 => Some(self.shader_stages.vertex.clone_weak()),
1 => self.shader_stages.fragment.as_ref().map(|h| h.clone_weak()),
_ => None,
};
self.state += 1;

View file

@ -1,4 +1,4 @@
use bevy_asset::{Assets, Handle};
use bevy_asset::{Asset, Assets, Handle};
use crate::{pipeline::RenderPipelines, Texture};
pub use bevy_derive::ShaderDefs;
@ -91,14 +91,14 @@ pub fn clear_shader_defs_system(mut query: Query<&mut RenderPipelines>) {
}
/// Updates [RenderPipelines] with the latest [ShaderDefs] from a given asset type
pub fn asset_shader_defs_system<T>(
pub fn asset_shader_defs_system<T: Asset>(
assets: Res<Assets<T>>,
mut query: Query<(&Handle<T>, &mut RenderPipelines)>,
) where
T: ShaderDefs + Send + Sync + 'static,
{
for (asset_handle, mut render_pipelines) in &mut query.iter() {
let shader_defs = assets.get(&asset_handle).unwrap();
let shader_defs = assets.get(asset_handle).unwrap();
for shader_def in shader_defs.iter_shader_defs() {
for render_pipeline in render_pipelines.pipelines.iter_mut() {
render_pipeline

View file

@ -1,15 +1,14 @@
use super::{Texture, TextureFormat};
use anyhow::Result;
use bevy_asset::AssetLoader;
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
use bevy_math::Vec2;
use std::path::Path;
/// Loads HDR textures as Texture assets
#[derive(Clone, Default)]
pub struct HdrTextureLoader;
impl AssetLoader<Texture> for HdrTextureLoader {
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<Texture> {
impl AssetLoader for HdrTextureLoader {
fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> {
let format = TextureFormat::Rgba32Float;
debug_assert_eq!(
format.pixel_size(),
@ -17,7 +16,7 @@ impl AssetLoader<Texture> for HdrTextureLoader {
"Format should have 32bit x 4 size"
);
let decoder = image::hdr::HdrDecoder::new(bytes.as_slice())?;
let decoder = image::hdr::HdrDecoder::new(bytes)?;
let info = decoder.metadata();
let rgb_data = decoder.read_image_hdr()?;
let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size());
@ -31,11 +30,14 @@ impl AssetLoader<Texture> for HdrTextureLoader {
rgba_data.extend_from_slice(&alpha.to_ne_bytes());
}
Ok(Texture::new(
let texture = Texture::new(
Vec2::new(info.width as f32, info.height as f32),
rgba_data,
format,
))
);
load_context.set_default_asset(LoadedAsset::new(texture));
Ok(())
}
fn extensions(&self) -> &[&str] {

View file

@ -1,8 +1,7 @@
use super::{Texture, TextureFormat};
use anyhow::Result;
use bevy_asset::AssetLoader;
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
use bevy_math::Vec2;
use std::path::Path;
/// Loader for images that can be read by the `image` crate.
///
@ -10,14 +9,14 @@ use std::path::Path;
#[derive(Clone, Default)]
pub struct ImageTextureLoader;
impl AssetLoader<Texture> for ImageTextureLoader {
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<Texture> {
impl AssetLoader for ImageTextureLoader {
fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> {
use bevy_core::AsBytes;
// Find the image type we expect. A file with the extension "png" should
// probably load as a PNG.
let ext = asset_path.extension().unwrap().to_str().unwrap();
let ext = load_context.path().extension().unwrap().to_str().unwrap();
// NOTE: If more formats are added they can be added here.
let img_format = if ext.eq_ignore_ascii_case("png") {
@ -26,7 +25,7 @@ impl AssetLoader<Texture> for ImageTextureLoader {
panic!(
"Unexpected image format {:?} for file {}, this is an error in `bevy_render`.",
ext,
asset_path.display()
load_context.path().display()
)
};
@ -36,7 +35,7 @@ impl AssetLoader<Texture> for ImageTextureLoader {
// needs to be added, so the image data needs to be converted in those
// cases.
let dyn_img = image::load_from_memory_with_format(bytes.as_slice(), img_format)?;
let dyn_img = image::load_from_memory_with_format(bytes, img_format)?;
let width;
let height;
@ -143,11 +142,9 @@ impl AssetLoader<Texture> for ImageTextureLoader {
}
}
Ok(Texture::new(
Vec2::new(width as f32, height as f32),
data,
format,
))
let texture = Texture::new(Vec2::new(width as f32, height as f32), data, format);
load_context.set_default_asset(LoadedAsset::new(texture));
Ok(())
}
fn extensions(&self) -> &[&str] {

View file

@ -6,12 +6,14 @@ use bevy_app::prelude::{EventReader, Events};
use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_ecs::{Res, ResMut};
use bevy_math::Vec2;
use bevy_type_registry::TypeUuid;
use bevy_utils::HashSet;
pub const TEXTURE_ASSET_INDEX: usize = 0;
pub const SAMPLER_ASSET_INDEX: usize = 1;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"]
pub struct Texture {
pub data: Vec<u8>,
pub size: Vec2,
@ -82,14 +84,14 @@ impl Texture {
for event in state.event_reader.iter(&texture_events) {
match event {
AssetEvent::Created { handle } => {
changed_textures.insert(*handle);
changed_textures.insert(handle);
}
AssetEvent::Modified { handle } => {
changed_textures.insert(*handle);
Self::remove_current_texture_resources(render_resource_context, *handle);
changed_textures.insert(handle);
Self::remove_current_texture_resources(render_resource_context, handle);
}
AssetEvent::Removed { handle } => {
Self::remove_current_texture_resources(render_resource_context, *handle);
Self::remove_current_texture_resources(render_resource_context, handle);
// if texture was modified and removed in the same update, ignore the modification
// events are ordered so future modification events are ok
changed_textures.remove(handle);
@ -98,7 +100,7 @@ impl Texture {
}
for texture_handle in changed_textures.iter() {
if let Some(texture) = textures.get(texture_handle) {
if let Some(texture) = textures.get(*texture_handle) {
let texture_descriptor: TextureDescriptor = texture.into();
let texture_resource = render_resource_context.create_texture(texture_descriptor);
@ -106,12 +108,12 @@ impl Texture {
let sampler_resource = render_resource_context.create_sampler(&sampler_descriptor);
render_resource_context.set_asset_resource(
*texture_handle,
texture_handle,
RenderResourceId::Texture(texture_resource),
TEXTURE_ASSET_INDEX,
);
render_resource_context.set_asset_resource(
*texture_handle,
texture_handle,
RenderResourceId::Sampler(sampler_resource),
SAMPLER_ASSET_INDEX,
);
@ -121,7 +123,7 @@ impl Texture {
fn remove_current_texture_resources(
render_resource_context: &dyn RenderResourceContext,
handle: Handle<Texture>,
handle: &Handle<Texture>,
) {
if let Some(RenderResourceId::Texture(resource)) =
render_resource_context.get_asset_resource(handle, TEXTURE_ASSET_INDEX)
@ -145,7 +147,7 @@ pub struct TextureResourceSystemState {
impl RenderResource for Option<Handle<Texture>> {
fn resource_type(&self) -> Option<RenderResourceType> {
self.map(|_texture| RenderResourceType::Texture)
self.as_ref().map(|_texture| RenderResourceType::Texture)
}
fn write_buffer_bytes(&self, _buffer: &mut [u8]) {}
@ -154,8 +156,8 @@ impl RenderResource for Option<Handle<Texture>> {
None
}
fn texture(&self) -> Option<Handle<Texture>> {
*self
fn texture(&self) -> Option<&Handle<Texture>> {
self.as_ref()
}
}
@ -170,7 +172,7 @@ impl RenderResource for Handle<Texture> {
None
}
fn texture(&self) -> Option<Handle<Texture>> {
Some(*self)
fn texture(&self) -> Option<&Handle<Texture>> {
Some(self)
}
}

View file

@ -0,0 +1,25 @@
use bevy_asset::Handle;
use bevy_ecs::{Command, Commands, Resources, World};
use crate::{Scene, SceneSpawner};
pub struct SpawnScene {
scene_handle: Handle<Scene>,
}
impl Command for SpawnScene {
fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
let mut spawner = resources.get_mut::<SceneSpawner>().unwrap();
spawner.spawn(self.scene_handle);
}
}
pub trait SpawnSceneCommands {
fn spawn_scene(&mut self, scene: Handle<Scene>) -> &mut Self;
}
impl SpawnSceneCommands for Commands {
fn spawn_scene(&mut self, scene_handle: Handle<Scene>) -> &mut Self {
self.add_command(SpawnScene { scene_handle })
}
}

View file

@ -0,0 +1,117 @@
use crate::{serde::SceneSerializer, Scene};
use anyhow::Result;
use bevy_ecs::{EntityMap, Resources, World};
use bevy_property::{DynamicProperties, PropertyTypeRegistry};
use bevy_type_registry::{ComponentRegistry, TypeRegistry, TypeUuid};
use serde::Serialize;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DynamicSceneToWorldError {
#[error("Scene contains an unregistered component.")]
UnregisteredComponent { type_name: String },
}
#[derive(Default, TypeUuid)]
#[uuid = "749479b1-fb8c-4ff8-a775-623aa76014f5"]
pub struct DynamicScene {
pub entities: Vec<Entity>,
}
pub struct Entity {
pub entity: u32,
pub components: Vec<DynamicProperties>,
}
impl DynamicScene {
pub fn from_scene(scene: &Scene, component_registry: &ComponentRegistry) -> Self {
Self::from_world(&scene.world, component_registry)
}
pub fn from_world(world: &World, component_registry: &ComponentRegistry) -> Self {
let mut scene = DynamicScene::default();
for archetype in world.archetypes() {
let mut entities = Vec::new();
for (index, entity) in archetype.iter_entities().enumerate() {
if index == entities.len() {
entities.push(Entity {
entity: entity.id(),
components: Vec::new(),
})
}
for type_info in archetype.types() {
if let Some(component_registration) = component_registry.get(&type_info.id()) {
let properties =
component_registration.get_component_properties(&archetype, index);
entities[index].components.push(properties.to_dynamic());
}
}
}
scene.entities.extend(entities.drain(..));
}
scene
}
pub fn write_to_world(
&self,
world: &mut World,
resources: &Resources,
) -> Result<(), DynamicSceneToWorldError> {
let type_registry = resources.get::<TypeRegistry>().unwrap();
let component_registry = type_registry.component.read();
let mut entity_map = EntityMap::default();
for scene_entity in self.entities.iter() {
let new_entity = world.reserve_entity();
entity_map.insert(bevy_ecs::Entity::new(scene_entity.entity), new_entity);
for component in scene_entity.components.iter() {
let component_registration = component_registry
.get_with_name(&component.type_name)
.ok_or_else(|| DynamicSceneToWorldError::UnregisteredComponent {
type_name: component.type_name.to_string(),
})?;
if world.has_component_type(new_entity, component_registration.ty) {
component_registration.apply_property_to_entity(world, new_entity, component);
} else {
component_registration
.add_property_to_entity(world, resources, new_entity, component);
}
}
}
for component_registration in component_registry.iter() {
component_registration
.map_entities(world, &entity_map)
.unwrap();
}
Ok(())
}
// TODO: move to AssetSaver when it is implemented
pub fn serialize_ron(&self, registry: &PropertyTypeRegistry) -> Result<String, ron::Error> {
serialize_ron(SceneSerializer::new(self, registry))
}
pub fn get_scene(&self, resources: &Resources) -> Result<Scene, DynamicSceneToWorldError> {
let mut world = World::default();
self.write_to_world(&mut world, resources)?;
Ok(Scene::new(world))
}
}
pub fn serialize_ron<S>(serialize: S) -> Result<String, ron::Error>
where
S: Serialize,
{
let pretty_config = ron::ser::PrettyConfig::default()
.with_decimal_floats(true)
.with_indentor(" ".to_string())
.with_new_line("\n".to_string());
let mut buf = Vec::new();
let mut ron_serializer = ron::ser::Serializer::new(&mut buf, Some(pretty_config), false)?;
serialize.serialize(&mut ron_serializer)?;
Ok(String::from_utf8(buf).unwrap())
}

View file

@ -1,14 +1,18 @@
mod loaded_scenes;
mod command;
mod dynamic_scene;
mod scene;
mod scene_loader;
mod scene_spawner;
pub mod serde;
pub use loaded_scenes::*;
pub use command::*;
pub use dynamic_scene::*;
pub use scene::*;
pub use scene_loader::*;
pub use scene_spawner::*;
pub mod prelude {
pub use crate::{Scene, SceneSpawner};
pub use crate::{DynamicScene, Scene, SceneSpawner, SpawnSceneCommands};
}
use bevy_app::prelude::*;
@ -22,8 +26,9 @@ pub const SCENE_STAGE: &str = "scene";
impl Plugin for ScenePlugin {
fn build(&self, app: &mut AppBuilder) {
app.add_asset::<Scene>()
.add_asset_loader::<Scene, SceneLoader>()
app.add_asset::<DynamicScene>()
.add_asset::<Scene>()
.init_asset_loader::<SceneLoader>()
.init_resource::<SceneSpawner>()
.add_stage_after(stage::EVENT_UPDATE, SCENE_STAGE)
.add_system_to_stage(SCENE_STAGE, scene_spawner_system.thread_local_system());

View file

@ -1,65 +1,14 @@
use crate::serde::SceneSerializer;
use anyhow::Result;
use bevy_ecs::World;
use bevy_property::{DynamicProperties, PropertyTypeRegistry};
use bevy_type_registry::ComponentRegistry;
use serde::Serialize;
use bevy_type_registry::TypeUuid;
#[derive(Debug, Default)]
#[derive(Debug, TypeUuid)]
#[uuid = "c156503c-edd9-4ec7-8d33-dab392df03cd"]
pub struct Scene {
pub entities: Vec<Entity>,
}
#[derive(Debug)]
pub struct Entity {
pub entity: u32,
pub components: Vec<DynamicProperties>,
pub world: World,
}
impl Scene {
pub fn from_world(world: &World, component_registry: &ComponentRegistry) -> Self {
let mut scene = Scene::default();
for archetype in world.archetypes() {
let mut entities = Vec::new();
for (index, entity) in archetype.iter_entities().enumerate() {
if index == entities.len() {
entities.push(Entity {
entity: entity.id(),
components: Vec::new(),
})
}
for type_info in archetype.types() {
if let Some(component_registration) = component_registry.get(&type_info.id()) {
let properties =
component_registration.get_component_properties(&archetype, index);
entities[index].components.push(properties.to_dynamic());
}
}
}
scene.entities.extend(entities.drain(..));
}
scene
}
// TODO: move to AssetSaver when it is implemented
pub fn serialize_ron(&self, registry: &PropertyTypeRegistry) -> Result<String, ron::Error> {
serialize_ron(SceneSerializer::new(self, registry))
pub fn new(world: World) -> Self {
Self { world }
}
}
pub fn serialize_ron<S>(serialize: S) -> Result<String, ron::Error>
where
S: Serialize,
{
let pretty_config = ron::ser::PrettyConfig::default()
.with_decimal_floats(true)
.with_indentor(" ".to_string())
.with_new_line("\n".to_string());
let mut buf = Vec::new();
let mut ron_serializer = ron::ser::Serializer::new(&mut buf, Some(pretty_config), false)?;
serialize.serialize(&mut ron_serializer)?;
Ok(String::from_utf8(buf).unwrap())
}

View file

@ -1,12 +1,12 @@
use crate::{serde::SceneDeserializer, Scene};
use crate::serde::SceneDeserializer;
use anyhow::Result;
use bevy_asset::AssetLoader;
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
use bevy_ecs::{FromResources, Resources};
use bevy_property::PropertyTypeRegistry;
use bevy_type_registry::TypeRegistry;
use parking_lot::RwLock;
use serde::de::DeserializeSeed;
use std::{path::Path, sync::Arc};
use std::sync::Arc;
#[derive(Debug)]
pub struct SceneLoader {
@ -22,15 +22,16 @@ impl FromResources for SceneLoader {
}
}
impl AssetLoader<Scene> for SceneLoader {
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<Scene> {
impl AssetLoader for SceneLoader {
fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> {
let registry = self.property_type_registry.read();
let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
let scene_deserializer = SceneDeserializer {
property_type_registry: &registry,
};
let scene = scene_deserializer.deserialize(&mut deserializer)?;
Ok(scene)
load_context.set_default_asset(LoadedAsset::new(scene));
Ok(())
}
fn extensions(&self) -> &[&str] {

View file

@ -1,7 +1,7 @@
use crate::Scene;
use crate::{DynamicScene, Scene};
use bevy_app::prelude::*;
use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_ecs::{Resources, World};
use bevy_ecs::{EntityMap, Resources, World};
use bevy_type_registry::TypeRegistry;
use bevy_utils::HashMap;
use thiserror::Error;
@ -9,7 +9,7 @@ use uuid::Uuid;
#[derive(Debug)]
struct InstanceInfo {
entity_map: HashMap<u32, bevy_ecs::Entity>,
entity_map: EntityMap,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
@ -24,10 +24,12 @@ impl InstanceId {
#[derive(Default)]
pub struct SceneSpawner {
spawned_scenes: HashMap<Handle<Scene>, Vec<InstanceId>>,
spawned_dynamic_scenes: HashMap<Handle<DynamicScene>, Vec<InstanceId>>,
spawned_instances: HashMap<InstanceId, InstanceInfo>,
scene_asset_event_reader: EventReader<AssetEvent<Scene>>,
scenes_to_instance: Vec<Handle<Scene>>,
scenes_to_despawn: Vec<Handle<Scene>>,
scene_asset_event_reader: EventReader<AssetEvent<DynamicScene>>,
dynamic_scenes_to_spawn: Vec<Handle<DynamicScene>>,
scenes_to_spawn: Vec<Handle<Scene>>,
scenes_to_despawn: Vec<Handle<DynamicScene>>,
}
#[derive(Error, Debug)]
@ -35,33 +37,99 @@ pub enum SceneSpawnError {
#[error("Scene contains an unregistered component.")]
UnregisteredComponent { type_name: String },
#[error("Scene does not exist. Perhaps it is still loading?")]
NonExistentScene { handle: Handle<Scene> },
NonExistentScene { handle: Handle<DynamicScene> },
#[error("Scene does not exist. Perhaps it is still loading?")]
NonExistentRealScene { handle: Handle<Scene> },
}
impl SceneSpawner {
pub fn spawn(&mut self, scene_handle: Handle<Scene>) {
self.scenes_to_instance.push(scene_handle);
pub fn spawn_dynamic(&mut self, scene_handle: Handle<DynamicScene>) {
self.dynamic_scenes_to_spawn.push(scene_handle);
}
pub fn despawn(&mut self, scene_handle: Handle<Scene>) {
pub fn spawn(&mut self, scene_handle: Handle<Scene>) {
self.scenes_to_spawn.push(scene_handle);
}
pub fn despawn(&mut self, scene_handle: Handle<DynamicScene>) {
self.scenes_to_despawn.push(scene_handle);
}
pub fn despawn_sync(
&mut self,
world: &mut World,
scene_handle: Handle<Scene>,
scene_handle: Handle<DynamicScene>,
) -> Result<(), SceneSpawnError> {
if let Some(instance_ids) = self.spawned_scenes.get(&scene_handle) {
if let Some(instance_ids) = self.spawned_dynamic_scenes.get(&scene_handle) {
for instance_id in instance_ids {
if let Some(instance) = self.spawned_instances.get(&instance_id) {
for entity in instance.entity_map.values() {
let _ = world.despawn(*entity); // Ignore the result, despawn only cares if it exists.
let _ = world.despawn(entity); // Ignore the result, despawn only cares if it exists.
}
}
}
self.spawned_scenes.remove(&scene_handle);
self.spawned_dynamic_scenes.remove(&scene_handle);
}
Ok(())
}
pub fn spawn_dynamic_sync(
&mut self,
world: &mut World,
resources: &Resources,
scene_handle: &Handle<DynamicScene>,
) -> Result<(), SceneSpawnError> {
let instance_id = InstanceId::new();
let mut instance_info = InstanceInfo {
entity_map: EntityMap::default(),
};
Self::spawn_dynamic_internal(world, resources, scene_handle, &mut instance_info)?;
self.spawned_instances.insert(instance_id, instance_info);
let spawned = self
.spawned_dynamic_scenes
.entry(scene_handle.clone())
.or_insert_with(Vec::new);
spawned.push(instance_id);
Ok(())
}
fn spawn_dynamic_internal(
world: &mut World,
resources: &Resources,
scene_handle: &Handle<DynamicScene>,
instance_info: &mut InstanceInfo,
) -> Result<(), SceneSpawnError> {
let type_registry = resources.get::<TypeRegistry>().unwrap();
let component_registry = type_registry.component.read();
let scenes = resources.get::<Assets<DynamicScene>>().unwrap();
let scene = scenes
.get(scene_handle)
.ok_or_else(|| SceneSpawnError::NonExistentScene {
handle: scene_handle.clone_weak(),
})?;
for scene_entity in scene.entities.iter() {
let entity = *instance_info
.entity_map
// TODO: use Entity type directly in scenes to properly encode generation / avoid the need to patch things up?
.entry(bevy_ecs::Entity::new(scene_entity.entity))
.or_insert_with(|| world.reserve_entity());
for component in scene_entity.components.iter() {
let component_registration = component_registry
.get_with_name(&component.type_name)
.ok_or(SceneSpawnError::UnregisteredComponent {
type_name: component.type_name.to_string(),
})?;
if world.has_component_type(entity, component_registration.ty) {
if component.type_name != "Camera" {
component_registration.apply_property_to_entity(world, entity, component);
}
} else {
component_registration
.add_property_to_entity(world, resources, entity, component);
}
}
}
Ok(())
}
@ -74,9 +142,42 @@ impl SceneSpawner {
) -> Result<(), SceneSpawnError> {
let instance_id = InstanceId::new();
let mut instance_info = InstanceInfo {
entity_map: HashMap::default(),
entity_map: EntityMap::default(),
};
Self::spawn_internal(world, resources, scene_handle, &mut instance_info)?;
let type_registry = resources.get::<TypeRegistry>().unwrap();
let component_registry = type_registry.component.read();
let scenes = resources.get::<Assets<Scene>>().unwrap();
let scene =
scenes
.get(&scene_handle)
.ok_or_else(|| SceneSpawnError::NonExistentRealScene {
handle: scene_handle.clone(),
})?;
for archetype in scene.world.archetypes() {
for scene_entity in archetype.iter_entities() {
let entity = *instance_info
.entity_map
.entry(*scene_entity)
.or_insert_with(|| world.reserve_entity());
for type_info in archetype.types() {
if let Some(component_registration) = component_registry.get(&type_info.id()) {
component_registration.component_copy(
&scene.world,
world,
resources,
*scene_entity,
entity,
);
}
}
}
}
for component_registration in component_registry.iter() {
component_registration
.map_entities(world, &instance_info.entity_map)
.unwrap();
}
self.spawned_instances.insert(instance_id, instance_info);
let spawned = self
.spawned_scenes
@ -86,56 +187,22 @@ impl SceneSpawner {
Ok(())
}
fn spawn_internal(
world: &mut World,
resources: &Resources,
scene_handle: Handle<Scene>,
instance_info: &mut InstanceInfo,
) -> Result<(), SceneSpawnError> {
let type_registry = resources.get::<TypeRegistry>().unwrap();
let component_registry = type_registry.component.read();
let scenes = resources.get::<Assets<Scene>>().unwrap();
let scene = scenes
.get(&scene_handle)
.ok_or(SceneSpawnError::NonExistentScene {
handle: scene_handle,
})?;
for scene_entity in scene.entities.iter() {
let entity = *instance_info
.entity_map
.entry(scene_entity.entity)
.or_insert_with(|| world.reserve_entity());
for component in scene_entity.components.iter() {
let component_registration = component_registry
.get_with_name(&component.type_name)
.ok_or(SceneSpawnError::UnregisteredComponent {
type_name: component.type_name.to_string(),
})?;
if world.has_component_type(entity, component_registration.ty) {
if component.type_name != "Camera" {
component_registration.apply_component_to_entity(world, entity, component);
}
} else {
component_registration
.add_component_to_entity(world, resources, entity, component);
}
}
}
Ok(())
}
pub fn update_spawned_scenes(
&mut self,
world: &mut World,
resources: &Resources,
scene_handles: &[Handle<Scene>],
scene_handles: &[Handle<DynamicScene>],
) -> Result<(), SceneSpawnError> {
for scene_handle in scene_handles {
if let Some(spawned_instances) = self.spawned_scenes.get(scene_handle) {
if let Some(spawned_instances) = self.spawned_dynamic_scenes.get(scene_handle) {
for instance_id in spawned_instances.iter() {
if let Some(instance_info) = self.spawned_instances.get_mut(instance_id) {
Self::spawn_internal(world, resources, *scene_handle, instance_info)?;
Self::spawn_dynamic_internal(
world,
resources,
scene_handle,
instance_info,
)?;
}
}
}
@ -157,13 +224,25 @@ impl SceneSpawner {
world: &mut World,
resources: &Resources,
) -> Result<(), SceneSpawnError> {
let scenes_to_spawn = std::mem::take(&mut self.scenes_to_instance);
let scenes_to_spawn = std::mem::take(&mut self.dynamic_scenes_to_spawn);
for scene_handle in scenes_to_spawn {
match self.spawn_dynamic_sync(world, resources, &scene_handle) {
Ok(_) => {}
Err(SceneSpawnError::NonExistentScene { .. }) => {
self.dynamic_scenes_to_spawn.push(scene_handle)
}
Err(err) => return Err(err),
}
}
let scenes_to_spawn = std::mem::take(&mut self.scenes_to_spawn);
for scene_handle in scenes_to_spawn {
match self.spawn_sync(world, resources, scene_handle) {
Ok(_) => {}
Err(SceneSpawnError::NonExistentScene { .. }) => {
self.scenes_to_instance.push(scene_handle)
Err(SceneSpawnError::NonExistentRealScene { handle }) => {
self.scenes_to_spawn.push(handle)
}
Err(err) => return Err(err),
}
@ -175,7 +254,7 @@ impl SceneSpawner {
pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) {
let mut scene_spawner = resources.get_mut::<SceneSpawner>().unwrap();
let scene_asset_events = resources.get::<Events<AssetEvent<Scene>>>().unwrap();
let scene_asset_events = resources.get::<Events<AssetEvent<DynamicScene>>>().unwrap();
let mut updated_spawned_scenes = Vec::new();
for event in scene_spawner
@ -183,8 +262,8 @@ pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) {
.iter(&scene_asset_events)
{
if let AssetEvent::Modified { handle } = event {
if scene_spawner.spawned_scenes.contains_key(handle) {
updated_spawned_scenes.push(*handle);
if scene_spawner.spawned_dynamic_scenes.contains_key(handle) {
updated_spawned_scenes.push(handle.clone_weak());
}
}
}

View file

@ -1,4 +1,4 @@
use crate::{Entity, Scene};
use crate::{DynamicScene, Entity};
use anyhow::Result;
use bevy_property::{
property_serde::{DynamicPropertiesDeserializer, DynamicPropertiesSerializer},
@ -11,12 +11,12 @@ use serde::{
};
pub struct SceneSerializer<'a> {
pub scene: &'a Scene,
pub scene: &'a DynamicScene,
pub registry: &'a PropertyTypeRegistry,
}
impl<'a> SceneSerializer<'a> {
pub fn new(scene: &'a Scene, registry: &'a PropertyTypeRegistry) -> Self {
pub fn new(scene: &'a DynamicScene, registry: &'a PropertyTypeRegistry) -> Self {
SceneSerializer { scene, registry }
}
}
@ -86,13 +86,13 @@ pub struct SceneDeserializer<'a> {
}
impl<'a, 'de> DeserializeSeed<'de> for SceneDeserializer<'a> {
type Value = Scene;
type Value = DynamicScene;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
let mut scene = Scene::default();
let mut scene = DynamicScene::default();
scene.entities = deserializer.deserialize_seq(SceneEntitySeqVisiter {
property_type_registry: self.property_type_registry,
})?;

View file

@ -1,7 +1,9 @@
use bevy_asset::{self, Handle};
use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture};
use bevy_type_registry::TypeUuid;
#[derive(Debug, RenderResources, ShaderDefs)]
#[derive(Debug, RenderResources, ShaderDefs, TypeUuid)]
#[uuid = "506cff92-a9f3-4543-862d-6851c7fdfc99"]
pub struct ColorMaterial {
pub color: Color,
#[shader_def]

View file

@ -33,12 +33,13 @@ use bevy_render::{
render_graph::RenderGraph,
shader::asset_shader_defs_system,
};
use bevy_type_registry::TypeUuid;
use sprite::sprite_system;
#[derive(Default)]
pub struct SpritePlugin;
pub const QUAD_HANDLE: Handle<Mesh> = Handle::from_u128(142404619811301375266013514540294236421);
pub const QUAD_HANDLE: Handle<Mesh> = Handle::weak_from_u64(Mesh::TYPE_UUID, 14240461981130137526);
impl Plugin for SpritePlugin {
fn build(&self, app: &mut AppBuilder) {
@ -50,18 +51,18 @@ impl Plugin for SpritePlugin {
asset_shader_defs_system::<ColorMaterial>.system(),
);
let resources = app.resources();
let resources = app.resources_mut();
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
render_graph.add_sprite_graph(resources);
let mut meshes = resources.get_mut::<Assets<Mesh>>().unwrap();
meshes.set(
let mut color_materials = resources.get_mut::<Assets<ColorMaterial>>().unwrap();
color_materials.set_untracked(Handle::<ColorMaterial>::default(), ColorMaterial::default());
meshes.set_untracked(
QUAD_HANDLE,
// Use a flipped quad because the camera is facing "forward" but quads should face backward
Mesh::from(shape::Quad::new(Vec2::new(1.0, 1.0))),
);
let mut color_materials = resources.get_mut::<Assets<ColorMaterial>>().unwrap();
color_materials.add_default(ColorMaterial::default());
)
}
}

View file

@ -11,12 +11,13 @@ use bevy_render::{
shader::{Shader, ShaderStage, ShaderStages},
texture::TextureFormat,
};
use bevy_type_registry::TypeUuid;
pub const SPRITE_PIPELINE_HANDLE: Handle<PipelineDescriptor> =
Handle::from_u128(278534784033876544639935131272264723170);
Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 2785347840338765446);
pub const SPRITE_SHEET_PIPELINE_HANDLE: Handle<PipelineDescriptor> =
Handle::from_u128(90168858051802816124217444474933884151);
Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 9016885805180281612);
pub fn build_sprite_sheet_pipeline(shaders: &mut Assets<Shader>) -> PipelineDescriptor {
PipelineDescriptor {
@ -150,8 +151,8 @@ impl SpriteRenderGraphBuilder for RenderGraph {
let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap();
pipelines.set(SPRITE_PIPELINE_HANDLE, build_sprite_pipeline(&mut shaders));
pipelines.set(
pipelines.set_untracked(SPRITE_PIPELINE_HANDLE, build_sprite_pipeline(&mut shaders));
pipelines.set_untracked(
SPRITE_SHEET_PIPELINE_HANDLE,
build_sprite_sheet_pipeline(&mut shaders),
);

View file

@ -3,8 +3,10 @@ use bevy_asset::{Assets, Handle};
use bevy_ecs::{Query, Res};
use bevy_math::Vec2;
use bevy_render::{renderer::RenderResources, texture::Texture};
use bevy_type_registry::TypeUuid;
#[derive(Debug, Default, RenderResources)]
#[derive(Debug, Default, RenderResources, TypeUuid)]
#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"]
pub struct Sprite {
pub size: Vec2,
#[render_resources(ignore)]
@ -43,9 +45,9 @@ pub fn sprite_system(
match sprite.resize_mode {
SpriteResizeMode::Manual => continue,
SpriteResizeMode::Automatic => {
let material = materials.get(&handle).unwrap();
if let Some(texture_handle) = material.texture {
if let Some(texture) = textures.get(&texture_handle) {
let material = materials.get(handle).unwrap();
if let Some(ref texture_handle) = material.texture {
if let Some(texture) = textures.get(texture_handle) {
sprite.size = texture.size;
}
}

View file

@ -7,10 +7,12 @@ use bevy_render::{
renderer::{RenderResource, RenderResources},
texture::Texture,
};
use bevy_type_registry::TypeUuid;
use bevy_utils::HashMap;
/// An atlas containing multiple textures (like a spritesheet or a tilemap)
#[derive(Debug, RenderResources)]
#[derive(Debug, RenderResources, TypeUuid)]
#[uuid = "946dacc5-c2b2-4b30-b81d-af77d79d1db7"]
pub struct TextureAtlas {
/// The handle to the texture in which the sprites are stored
pub texture: Handle<Texture>,
@ -138,9 +140,9 @@ impl TextureAtlas {
self.textures.is_empty()
}
pub fn get_texture_index(&self, texture: Handle<Texture>) -> Option<usize> {
pub fn get_texture_index(&self, texture: &Handle<Texture>) -> Option<usize> {
self.texture_handles
.as_ref()
.and_then(|texture_handles| texture_handles.get(&texture).cloned())
.and_then(|texture_handles| texture_handles.get(texture).cloned())
}
}

View file

@ -123,7 +123,7 @@ impl TextureAtlasBuilder {
packed_location.width() as f32,
packed_location.height() as f32,
);
texture_handles.insert(*texture_handle, texture_rects.len());
texture_handles.insert(texture_handle.clone_weak(), texture_rects.len());
texture_rects.push(Rect { min, max });
self.place_texture(&mut atlas_texture, texture, packed_location);
}

View file

@ -20,6 +20,7 @@ bevy_core = { path = "../bevy_core", version = "0.2.1" }
bevy_math = { path = "../bevy_math", version = "0.2.1" }
bevy_render = { path = "../bevy_render", version = "0.2.1" }
bevy_sprite = { path = "../bevy_sprite", version = "0.2.1" }
bevy_type_registry = { path = "../bevy_type_registry", version = "0.2.1" }
bevy_utils = { path = "../bevy_utils", version = "0.2.1" }
# other

View file

@ -47,7 +47,7 @@ impl<'a> Drawable for DrawableText<'a> {
fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> {
context.set_pipeline(
draw,
bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE,
&bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE,
&PipelineSpecialization {
sample_count: self.msaa.samples,
..Default::default()
@ -56,13 +56,13 @@ impl<'a> Drawable for DrawableText<'a> {
let render_resource_context = &**context.render_resource_context;
if let Some(RenderResourceId::Buffer(quad_vertex_buffer)) = render_resource_context
.get_asset_resource(bevy_sprite::QUAD_HANDLE, mesh::VERTEX_BUFFER_ASSET_INDEX)
.get_asset_resource(&bevy_sprite::QUAD_HANDLE, mesh::VERTEX_BUFFER_ASSET_INDEX)
{
draw.set_vertex_buffer(0, quad_vertex_buffer, 0);
}
let mut indices = 0..0;
if let Some(RenderResourceId::Buffer(quad_index_buffer)) = render_resource_context
.get_asset_resource(bevy_sprite::QUAD_HANDLE, mesh::INDEX_BUFFER_ASSET_INDEX)
.get_asset_resource(&bevy_sprite::QUAD_HANDLE, mesh::INDEX_BUFFER_ASSET_INDEX)
{
draw.set_index_buffer(quad_index_buffer, 0);
if let Some(buffer_info) = render_resource_context.get_buffer_info(quad_index_buffer) {
@ -111,7 +111,7 @@ impl<'a> Drawable for DrawableText<'a> {
let glyph_height = glyph_rect.height();
let atlas_render_resource_bindings = self
.asset_render_resource_bindings
.get_mut(glyph_atlas_info.texture_atlas)
.get_mut(&glyph_atlas_info.texture_atlas)
.unwrap();
context.set_bind_groups_from_bindings(
draw,

View file

@ -4,8 +4,10 @@ use bevy_render::{
color::Color,
texture::{Texture, TextureFormat},
};
use bevy_type_registry::TypeUuid;
#[derive(Debug)]
#[derive(Debug, TypeUuid)]
#[uuid = "97059ac6-c9ba-4da9-95b6-bed82c3ce198"]
pub struct Font {
pub font: FontVec,
}

View file

@ -5,12 +5,14 @@ use bevy_core::FloatOrd;
use bevy_math::Vec2;
use bevy_render::texture::Texture;
use bevy_sprite::TextureAtlas;
use bevy_type_registry::TypeUuid;
use bevy_utils::HashMap;
// work around rust's f32 order/hash limitations
type FontSizeKey = FloatOrd;
#[derive(Default)]
#[derive(Default, TypeUuid)]
#[uuid = "73ba778b-b6b5-4f45-982d-d21b6b86ace2"]
pub struct FontAtlasSet {
font: Handle<Font>,
font_atlases: HashMap<FontSizeKey, Vec<FontAtlas>>,
@ -117,7 +119,7 @@ impl FontAtlasSet {
.find_map(|atlas| {
atlas
.get_char_index(character)
.map(|char_index| (char_index, atlas.texture_atlas))
.map(|char_index| (char_index, atlas.texture_atlas.clone_weak()))
})
.map(|(char_index, texture_atlas)| GlyphAtlasInfo {
texture_atlas,

Some files were not shown because too many files have changed in this diff Show more