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" path = "examples/2d/texture_atlas.rs"
[[example]] [[example]]
name = "load_model" name = "load_gltf"
path = "examples/3d/load_model.rs" path = "examples/3d/load_gltf.rs"
[[example]] [[example]]
name = "msaa" name = "msaa"
@ -171,8 +171,8 @@ name = "asset_loading"
path = "examples/asset/asset_loading.rs" path = "examples/asset/asset_loading.rs"
[[example]] [[example]]
name = "custom_loader" name = "custom_asset"
path = "examples/asset/custom_asset_loading.rs" path = "examples/asset/custom_asset.rs"
[[example]] [[example]]
name = "audio" 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 { 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_startup_stage(startup_stage::POST_STARTUP)
.add_stage(stage::FIRST) .add_stage(stage::FIRST)
.add_stage(stage::EVENT_UPDATE) .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 /// Name of app stage that runs once when an app starts up
pub const STARTUP: &str = "startup"; 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"; pub const POST_STARTUP: &str = "post_startup";

View file

@ -28,16 +28,12 @@ bevy_utils = { path = "../bevy_utils", version = "0.2.1" }
# other # other
uuid = { version = "0.8", features = ["v4", "serde"] } uuid = { version = "0.8", features = ["v4", "serde"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
ron = "0.6.2"
crossbeam-channel = "0.4.4" crossbeam-channel = "0.4.4"
anyhow = "1.0" anyhow = "1.0"
thiserror = "1.0" thiserror = "1.0"
downcast-rs = "1.2.0"
log = { version = "0.4", features = ["release_max_level_info"] } log = { version = "0.4", features = ["release_max_level_info"] }
notify = { version = "5.0.0-pre.2", optional = true } notify = { version = "5.0.0-pre.2", optional = true }
parking_lot = "0.11.0" parking_lot = "0.11.0"
async-trait = "0.1.40" rand = "0.7.3"
[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"

View file

@ -1,414 +1,438 @@
use crate::{ use crate::{
filesystem_watcher::FilesystemWatcher, AssetLoadError, AssetLoadRequestHandler, AssetLoader, path::{AssetPath, AssetPathId, SourcePathId},
Assets, Handle, HandleId, LoadRequest, Asset, AssetIo, AssetIoError, AssetLifecycle, AssetLifecycleChannel, AssetLifecycleEvent,
AssetLoader, Assets, FileAssetIo, Handle, HandleId, HandleUntyped, LabelId, LoadContext,
LoadState, RefChange, RefChangeChannel, SourceInfo, SourceMeta,
}; };
use anyhow::Result; use anyhow::Result;
use bevy_ecs::{Res, Resource, Resources}; use bevy_ecs::Res;
use bevy_tasks::TaskPool; use bevy_tasks::TaskPool;
use bevy_utils::{HashMap, HashSet}; use bevy_utils::HashMap;
use crossbeam_channel::TryRecvError; use crossbeam_channel::TryRecvError;
use parking_lot::RwLock; use parking_lot::RwLock;
use std::{ use std::{collections::hash_map::Entry, path::Path, sync::Arc};
fs, io,
path::{Path, PathBuf},
sync::Arc,
};
use thiserror::Error; use thiserror::Error;
use uuid::Uuid;
/// The type used for asset versioning
pub type AssetVersion = usize;
/// Errors that occur while loading assets with an AssetServer /// Errors that occur while loading assets with an AssetServer
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AssetServerError { pub enum AssetServerError {
#[error("Asset folder path is not a directory.")] #[error("Asset folder path is not a directory.")]
AssetFolderNotADirectory(String), AssetFolderNotADirectory(String),
#[error("Invalid root path")]
InvalidRootPath,
#[error("No AssetHandler found for the given extension.")]
MissingAssetHandler,
#[error("No AssetLoader found for the given extension.")] #[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.")] #[error("Encountered an error while loading an asset.")]
AssetLoadError(#[from] AssetLoadError), AssetLoaderError(anyhow::Error),
#[error("Encountered an io error.")] #[error("PathLoader encountered an error")]
Io(#[from] io::Error), PathLoaderError(#[from] AssetIoError),
#[error("Failed to watch asset folder.")]
AssetWatchError { path: PathBuf },
} }
/// Info about a specific asset, such as its path and its current load state #[derive(Default)]
#[derive(Clone, Debug)] pub(crate) struct AssetRefCounter {
pub struct AssetInfo { pub(crate) channel: Arc<RefChangeChannel>,
pub handle_id: HandleId, pub(crate) ref_counts: Arc<RwLock<HashMap<HandleId, usize>>>,
pub path: PathBuf,
pub load_state: LoadState,
} }
/// The load state of an asset pub struct AssetServerInternal<TAssetIo: AssetIo = FileAssetIo> {
#[derive(Clone, Debug, Eq, PartialEq)] pub(crate) asset_io: TAssetIo,
pub enum LoadState { pub(crate) asset_ref_counter: AssetRefCounter,
Loading(AssetVersion), pub(crate) asset_sources: Arc<RwLock<HashMap<SourcePathId, SourceInfo>>>,
Loaded(AssetVersion), pub(crate) asset_lifecycles: Arc<RwLock<HashMap<Uuid, Box<dyn AssetLifecycle>>>>,
Failed(AssetVersion), loaders: RwLock<Vec<Arc<Box<dyn AssetLoader>>>>,
} extension_to_loader_index: RwLock<HashMap<String, usize>>,
handle_to_path: Arc<RwLock<HashMap<HandleId, AssetPath<'static>>>>,
impl LoadState { task_pool: TaskPool,
pub fn get_version(&self) -> AssetVersion {
match *self {
LoadState::Loaded(version) => version,
LoadState::Loading(version) => version,
LoadState::Failed(version) => version,
}
}
} }
/// Loads assets from the filesystem on background threads /// Loads assets from the filesystem on background threads
pub struct AssetServer { pub struct AssetServer<TAssetIo: AssetIo = FileAssetIo> {
asset_folders: RwLock<Vec<PathBuf>>, pub(crate) server: Arc<AssetServerInternal<TAssetIo>>,
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>>>,
} }
impl AssetServer { impl<TAssetIo: AssetIo> Clone for AssetServer<TAssetIo> {
pub fn new(task_pool: TaskPool) -> Self { 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 { AssetServer {
asset_folders: Default::default(), server: Arc::new(AssetServerInternal {
asset_handlers: Default::default(), loaders: Default::default(),
loaders: Default::default(), extension_to_loader_index: Default::default(),
extension_to_handler_index: Default::default(), asset_sources: Default::default(),
extension_to_loader_index: Default::default(), asset_ref_counter: Default::default(),
asset_info_paths: Default::default(), handle_to_path: Default::default(),
asset_info: Default::default(), asset_lifecycles: Default::default(),
task_pool, task_pool,
#[cfg(feature = "filesystem_watcher")] asset_io: source_io,
filesystem_watcher: Arc::new(RwLock::new(None)), }),
} }
} }
pub fn add_handler<T>(&mut self, asset_handler: T) pub(crate) fn register_asset_type<T: Asset>(&self) -> Assets<T> {
where self.server.asset_lifecycles.write().insert(
T: AssetLoadRequestHandler, T::TYPE_UUID,
{ Box::new(AssetLifecycleChannel::<T>::default()),
let mut asset_handlers = self.asset_handlers.write(); );
let handler_index = asset_handlers.len(); Assets::new(self.server.asset_ref_counter.channel.sender.clone())
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 fn add_loader<TLoader, TAsset>(&mut self, loader: TLoader) pub fn add_loader<T>(&self, loader: T)
where where
TLoader: AssetLoader<TAsset>, T: AssetLoader,
TAsset: 'static,
{ {
let loader_index = self.loaders.len(); let mut loaders = self.server.loaders.write();
let loader_index = loaders.len();
for extension in loader.extensions().iter() { for extension in loader.extensions().iter() {
self.extension_to_loader_index self.server
.extension_to_loader_index
.write()
.insert(extension.to_string(), loader_index); .insert(extension.to_string(), loader_index);
} }
loaders.push(Arc::new(Box::new(loader)));
let mut resources = Resources::default();
resources.insert::<Box<dyn AssetLoader<TAsset>>>(Box::new(loader));
self.loaders.push(resources);
} }
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> { pub fn watch_for_changes(&self) -> Result<(), AssetServerError> {
let mut filesystem_watcher = self.filesystem_watcher.write(); self.server.asset_io.watch_for_changes()?;
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)?;
}
Ok(()) Ok(())
} }
#[cfg(not(target_arch = "wasm32"))] pub fn get_handle<T: Asset, I: Into<HandleId>>(&self, id: I) -> Handle<T> {
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> { let sender = self.server.asset_ref_counter.channel.sender.clone();
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") { Handle::strong(id.into(), sender)
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)),
}
}
} }
#[cfg(target_arch = "wasm32")] pub fn get_handle_untyped<I: Into<HandleId>>(&self, id: I) -> HandleUntyped {
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> { let sender = self.server.asset_ref_counter.channel.sender.clone();
Ok(PathBuf::from("/")) HandleUntyped::strong(id.into(), sender)
} }
// TODO: add type checking here. people shouldn't be able to request a Handle<Texture> for a Mesh asset fn get_asset_loader(
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>>(
&self, &self,
assets: &mut Assets<T>, extension: &str,
path: P, ) -> Result<Arc<Box<dyn AssetLoader>>, AssetServerError> {
) -> Result<Handle<T>, AssetServerError> self.server
where .extension_to_loader_index
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
.read() .read()
.get(&handle_id) .get(extension)
.map(|asset_info| asset_info.load_state.clone()) .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> { fn get_path_asset_loader<P: AsRef<Path>>(
self.get_load_state_untyped(handle.id) &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> { pub fn get_handle_path<H: Into<HandleId>>(&self, handle: H) -> Option<AssetPath<'_>> {
let mut load_state = LoadState::Loaded(0); self.server
for handle_id in handle_ids.iter() { .handle_to_path
match self.get_load_state_untyped(*handle_id) { .read()
Some(LoadState::Loaded(_)) => continue, .get(&handle.into())
Some(LoadState::Loading(_)) => { .cloned()
load_state = LoadState::Loading(0); }
}
Some(LoadState::Failed(_)) => return Some(LoadState::Failed(0)), pub fn get_load_state<H: Into<HandleId>>(&self, handle: H) -> LoadState {
None => return None, 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, &self,
path: &Path, path: P,
) -> Result<Vec<HandleId>, AssetServerError> { force: bool,
if !path.is_dir() { ) -> 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( return Err(AssetServerError::AssetFolderNotADirectory(
path.to_str().unwrap().to_string(), path.to_str().unwrap().to_string(),
)); ));
} }
let root_path = self.get_root_path()?; let mut handles = Vec::new();
let mut handle_ids = Vec::new(); for child_path in self.server.asset_io.read_directory(path.as_ref())? {
for entry in fs::read_dir(path)? { if self.server.asset_io.is_directory(&child_path) {
let entry = entry?; handles.extend(self.load_folder(&child_path)?);
let child_path = entry.path();
if child_path.is_dir() {
handle_ids.extend(self.load_assets_in_folder_recursive(&child_path)?);
} else { } else {
let relative_child_path = child_path.strip_prefix(&root_path).unwrap(); if self.get_path_asset_loader(&child_path).is_err() {
let handle = match self.load_untyped( continue;
relative_child_path }
.to_str() let handle =
.expect("Path should be a valid string"), self.load_untyped(child_path.to_str().expect("Path should be a valid string"));
) { handles.push(handle);
Ok(handle) => handle,
Err(AssetServerError::MissingAssetHandler) => continue,
Err(err) => return Err(err),
};
handle_ids.push(handle);
} }
} }
Ok(handle_ids) Ok(handles)
} }
}
#[cfg(feature = "filesystem_watcher")] pub fn free_unused_assets(&self) {
pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) { let receiver = &self.server.asset_ref_counter.channel.receiver;
let mut changed = HashSet::default(); let mut ref_counts = self.server.asset_ref_counter.ref_counts.write();
let asset_sources = self.server.asset_sources.read();
loop { let mut potential_frees = Vec::new();
let result = { loop {
let rwlock_guard = asset_server.filesystem_watcher.read(); let ref_change = match receiver.try_recv() {
if let Some(filesystem_watcher) = rwlock_guard.as_ref() { Ok(ref_change) => ref_change,
filesystem_watcher.receiver.try_recv() Err(TryRecvError::Empty) => break,
} else { Err(TryRecvError::Disconnected) => panic!("RefChange channel disconnected"),
break; };
} match ref_change {
}; RefChange::Increment(handle_id) => *ref_counts.entry(handle_id).or_insert(0) += 1,
let event = match result { RefChange::Decrement(handle_id) => {
Ok(result) => result.unwrap(), let entry = ref_counts.entry(handle_id).or_insert(0);
Err(TryRecvError::Empty) => break, *entry -= 1;
Err(TryRecvError::Disconnected) => panic!("FilesystemWatcher disconnected"), if *entry == 0 {
}; potential_frees.push(handle_id);
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(_) => {}
} }
} }
} }
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::{ use crate::{
update_asset_storage_system, AssetChannel, AssetLoader, AssetServer, ChannelAssetHandler, update_asset_storage_system, Asset, AssetLoader, AssetServer, Handle, HandleId, RefChange,
Handle, HandleId,
}; };
use bevy_app::{prelude::Events, AppBuilder}; 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_type_registry::RegisterType;
use bevy_utils::HashMap; use bevy_utils::HashMap;
use crossbeam_channel::Sender;
use std::fmt::Debug;
/// Events that happen on assets of type `T` /// Events that happen on assets of type `T`
#[derive(Debug)] pub enum AssetEvent<T: Asset> {
pub enum AssetEvent<T: Resource> {
Created { handle: Handle<T> }, Created { handle: Handle<T> },
Modified { handle: Handle<T> }, Modified { handle: Handle<T> },
Removed { handle: Handle<T> }, Removed { handle: Handle<T> },
} }
/// Stores Assets of a given type and tracks changes to them. impl<T: Asset> Debug for AssetEvent<T> {
#[derive(Debug)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
pub struct Assets<T: Resource> { match self {
assets: HashMap<Handle<T>, T>, AssetEvent::Created { handle } => f
events: Events<AssetEvent<T>>, .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> { /// Stores Assets of a given type and tracks changes to them.
fn default() -> Self { #[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 {
assets: HashMap::default(), assets: HashMap::default(),
events: Events::default(), events: Events::default(),
ref_change_sender,
} }
} }
}
impl<T: Resource> Assets<T> {
pub fn add(&mut self, asset: T) -> Handle<T> { pub fn add(&mut self, asset: T) -> Handle<T> {
let handle = Handle::new(); let id = HandleId::random::<T>();
self.assets.insert(handle, asset); self.assets.insert(id, asset);
self.events.send(AssetEvent::Created { handle }); self.events.send(AssetEvent::Created {
handle handle: Handle::weak(id),
});
self.get_handle(id)
} }
pub fn set(&mut self, handle: Handle<T>, asset: T) { pub fn set<H: Into<HandleId>>(&mut self, handle: H, asset: T) -> Handle<T> {
let exists = self.assets.contains_key(&handle); let id: HandleId = handle.into();
self.assets.insert(handle, asset); if self.assets.insert(id, asset).is_some() {
self.events.send(AssetEvent::Modified {
if exists { handle: Handle::weak(id),
self.events.send(AssetEvent::Modified { handle }); });
} else { } 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> { pub fn get<H: Into<HandleId>>(&self, handle: H) -> Option<&T> {
let handle = Handle::default(); self.assets.get(&handle.into())
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_with_id(&self, id: HandleId) -> Option<&T> { pub fn contains<H: Into<HandleId>>(&self, handle: H) -> bool {
self.get(&Handle::from_id(id)) self.assets.contains_key(&handle.into())
} }
pub fn get_with_id_mut(&mut self, id: HandleId) -> Option<&mut T> { pub fn get_mut<H: Into<HandleId>>(&mut self, handle: H) -> Option<&mut T> {
self.get_mut(&Handle::from_id(id)) 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> { pub fn get_handle<H: Into<HandleId>>(&self, handle: H) -> Handle<T> {
self.assets.get(&handle) Handle::strong(handle.into(), self.ref_change_sender.clone())
} }
pub fn get_mut(&mut self, handle: &Handle<T>) -> Option<&mut T> { pub fn get_or_insert_with<H: Into<HandleId>>(
self.events.send(AssetEvent::Modified { handle: *handle });
self.assets.get_mut(&handle)
}
pub fn get_or_insert_with(
&mut self, &mut self,
handle: Handle<T>, handle: H,
insert_fn: impl FnOnce() -> T, insert_fn: impl FnOnce() -> T,
) -> &mut T { ) -> &mut T {
let mut event = None; let mut event = None;
let borrowed = self.assets.entry(handle).or_insert_with(|| { let id: HandleId = handle.into();
event = Some(AssetEvent::Created { handle }); let borrowed = self.assets.entry(id).or_insert_with(|| {
event = Some(AssetEvent::Created {
handle: Handle::weak(id),
});
insert_fn() insert_fn()
}); });
@ -96,12 +137,23 @@ impl<T: Resource> Assets<T> {
borrowed 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)) self.assets.iter().map(|(k, v)| (*k, v))
} }
pub fn remove(&mut self, handle: &Handle<T>) -> Option<T> { pub fn ids(&self) -> impl Iterator<Item = HandleId> + '_ {
self.assets.remove(&handle) 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. /// Clears the inner asset map, removing all key-value pairs.
@ -132,75 +184,67 @@ impl<T: Resource> Assets<T> {
) { ) {
events.extend(assets.events.drain()) 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 /// [AppBuilder] extension methods for adding new asset types
pub trait AddAsset { pub trait AddAsset {
fn add_asset<T>(&mut self) -> &mut Self fn add_asset<T>(&mut self) -> &mut Self
where where
T: Send + Sync + 'static; T: Asset;
fn add_asset_loader<TAsset, TLoader>(&mut self) -> &mut Self fn init_asset_loader<T>(&mut self) -> &mut Self
where where
TLoader: AssetLoader<TAsset> + FromResources, T: AssetLoader + FromResources;
TAsset: Send + Sync + 'static; fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
fn add_asset_loader_from_instance<TAsset, TLoader>(&mut self, instance: TLoader) -> &mut Self
where where
TLoader: AssetLoader<TAsset> + FromResources, T: AssetLoader;
TAsset: Send + Sync + 'static;
} }
impl AddAsset for AppBuilder { impl AddAsset for AppBuilder {
fn add_asset<T>(&mut self) -> &mut Self fn add_asset<T>(&mut self) -> &mut Self
where 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>>() .register_component::<Handle<T>>()
.add_system_to_stage( .add_system_to_stage(
super::stage::ASSET_EVENTS, super::stage::ASSET_EVENTS,
Assets::<T>::asset_event_system.system(), Assets::<T>::asset_event_system.system(),
) )
.add_system_to_stage(
crate::stage::LOAD_ASSETS,
update_asset_storage_system::<T>.system(),
)
.add_event::<AssetEvent<T>>() .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 where
TLoader: AssetLoader<TAsset> + FromResources, T: AssetLoader + FromResources,
TAsset: Send + Sync + 'static,
{ {
{ self.add_asset_loader(T::from_resources(self.resources()))
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
} }
fn add_asset_loader<TAsset, TLoader>(&mut self) -> &mut Self fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
where where
TLoader: AssetLoader<TAsset> + FromResources, T: AssetLoader,
TAsset: Send + Sync + 'static,
{ {
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, cmp::Ordering,
fmt::Debug, fmt::Debug,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
marker::PhantomData,
}; };
use bevy_property::{Properties, Property}; use bevy_property::{Properties, Property};
use crossbeam_channel::{Receiver, Sender};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{any::TypeId, marker::PhantomData};
use uuid::Uuid; use uuid::Uuid;
/// The ID of the "default" asset use crate::{
pub(crate) const DEFAULT_HANDLE_ID: HandleId = path::{AssetPath, AssetPathId},
HandleId(Uuid::from_u128(240940089166493627844978703213080810552)); Asset, Assets,
};
/// A unique id that corresponds to a specific asset in the [Assets](crate::Assets) collection. /// A unique, stable asset id
#[derive( #[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 { impl HandleId {
#[allow(clippy::new_without_default)] #[inline]
pub fn new() -> HandleId { pub fn random<T: Asset>() -> Self {
HandleId(Uuid::new_v4()) 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, pub id: HandleId,
#[property(ignore)] #[property(ignore)]
handle_type: HandleType,
#[property(ignore)]
marker: PhantomData<T>, 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> { impl<T> Handle<T> {
pub fn new() -> Self { // TODO: remove "uuid" parameter whenever rust support type constraints in const fns
Handle { pub const fn weak_from_u64(uuid: Uuid, id: u64) -> Self {
id: HandleId::new(), Self {
id: HandleId::new(uuid, id),
handle_type: HandleType::Weak,
marker: PhantomData, marker: PhantomData,
} }
} }
}
/// Gets a handle for the given type that has this handle's id. This is useful when an impl<T: Asset> Handle<T> {
/// asset is derived from another asset. In this case, a common handle can be used to pub(crate) fn strong(id: HandleId, ref_change_sender: Sender<RefChange>) -> Self {
/// correlate them. ref_change_sender.send(RefChange::Increment(id)).unwrap();
/// NOTE: This pattern might eventually be replaced by a more formal asset dependency system. Self {
pub fn as_handle<U>(&self) -> Handle<U> {
Handle::from_id(self.id)
}
pub const fn from_id(id: HandleId) -> Self {
Handle {
id, id,
handle_type: HandleType::Strong(ref_change_sender),
marker: PhantomData, 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 { Handle {
id: HandleId(Uuid::from_u128(value)), id: self.id,
handle_type: HandleType::Weak,
marker: PhantomData, marker: PhantomData,
} }
} }
pub const fn from_bytes(bytes: [u8; 16]) -> Self { pub fn is_weak(&self) -> bool {
Handle { matches!(self.handle_type, HandleType::Weak)
id: HandleId(Uuid::from_bytes(bytes)), }
marker: PhantomData,
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>> pub fn clone_weak_untyped(&self) -> HandleUntyped {
where HandleUntyped::weak(self.id)
T: 'static,
{
if TypeId::of::<T>() == untyped_handle.type_id {
Some(Handle::from_id(untyped_handle.id))
} else {
None
}
} }
} }
impl<T> From<HandleId> for Handle<T> { impl<T> Drop for Handle<T> {
fn from(value: HandleId) -> Self { fn drop(&mut self) {
Handle::from_id(value) 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));
impl<T> From<u128> for Handle<T> {
fn from(value: u128) -> Self {
Handle::from_u128(value)
}
}
impl<T> From<[u8; 16]> for Handle<T> {
fn from(value: [u8; 16]) -> Self {
Handle::from_bytes(value)
}
}
impl<T> From<HandleUntyped> for Handle<T>
where
T: 'static,
{
fn from(handle: HandleUntyped) -> Self {
if TypeId::of::<T>() == handle.type_id {
Handle {
id: handle.id,
marker: PhantomData::default(),
} }
} else { HandleType::Weak => {}
panic!("attempted to convert untyped handle to incorrect typed handle")
} }
} }
} }
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> { impl<T> Hash for Handle<T> {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state); 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> { impl<T> Debug for Handle<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let name = std::any::type_name::<T>().split("::").last().unwrap(); 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> { impl<T: Asset> Clone for Handle<T> {
fn default() -> Self {
Handle {
id: DEFAULT_HANDLE_ID,
marker: PhantomData,
}
}
}
impl<T> Clone for Handle<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Handle { match self.handle_type {
id: self.id, HandleType::Strong(ref sender) => Handle::strong(self.id, sender.clone()),
marker: PhantomData, HandleType::Weak => Handle::weak(self.id),
} }
} }
} }
impl<T> Copy for Handle<T> {}
// SAFE: T is phantom data and Handle::id is an integer // SAFE: T is phantom data and Handle::id is an integer
unsafe impl<T> Send for Handle<T> {} unsafe impl<T> Send for Handle<T> {}
@ -181,26 +237,115 @@ unsafe impl<T> Sync for Handle<T> {}
/// A non-generic version of [Handle] /// 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>`. /// 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 struct HandleUntyped {
pub id: HandleId, pub id: HandleId,
pub type_id: TypeId, handle_type: HandleType,
} }
impl HandleUntyped { impl HandleUntyped {
pub fn is_handle<T: 'static>(untyped: &HandleUntyped) -> bool { pub(crate) fn strong(id: HandleId, ref_change_sender: Sender<RefChange>) -> Self {
TypeId::of::<T>() == untyped.type_id ref_change_sender.send(RefChange::Increment(id)).unwrap();
Self {
id,
handle_type: HandleType::Strong(ref_change_sender),
}
} }
}
impl<T> From<Handle<T>> for HandleUntyped pub fn weak(id: HandleId) -> Self {
where Self {
T: 'static, id,
{ handle_type: HandleType::Weak,
fn from(handle: Handle<T>) -> Self { }
HandleUntyped { }
id: handle.id,
type_id: TypeId::of::<T>(), 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")] #[cfg(feature = "filesystem_watcher")]
mod filesystem_watcher; mod filesystem_watcher;
mod handle; mod handle;
mod load_request; mod info;
mod io;
mod loader; mod loader;
mod path;
pub use asset_server::*; pub use asset_server::*;
pub use assets::*; pub use assets::*;
use bevy_tasks::IoTaskPool; use bevy_tasks::IoTaskPool;
pub use handle::*; pub use handle::*;
pub use load_request::*; pub use info::*;
pub use io::*;
pub use loader::*; pub use loader::*;
pub use path::*;
/// The names of asset stages in an App Schedule /// The names of asset stages in an App Schedule
pub mod stage { pub mod stage {
@ -20,7 +24,7 @@ pub mod stage {
} }
pub mod prelude { 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}; use bevy_app::{prelude::Plugin, AppBuilder};
@ -32,6 +36,18 @@ use bevy_type_registry::RegisterType;
#[derive(Default)] #[derive(Default)]
pub struct AssetPlugin; 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 { impl Plugin for AssetPlugin {
fn build(&self, app: &mut AppBuilder) { fn build(&self, app: &mut AppBuilder) {
let task_pool = app let task_pool = app
@ -40,15 +56,25 @@ impl Plugin for AssetPlugin {
.expect("IoTaskPool resource not found") .expect("IoTaskPool resource not found")
.0 .0
.clone(); .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) app.add_stage_before(bevy_app::stage::PRE_UPDATE, stage::LOAD_ASSETS)
.add_stage_after(bevy_app::stage::POST_UPDATE, stage::ASSET_EVENTS) .add_stage_after(bevy_app::stage::POST_UPDATE, stage::ASSET_EVENTS)
.add_resource(AssetServer::new(task_pool)) .add_resource(asset_server)
.register_property::<HandleId>(); .register_property::<HandleId>()
.add_system_to_stage(
bevy_app::stage::PRE_UPDATE,
asset_server::free_unused_assets_system.system(),
);
#[cfg(feature = "filesystem_watcher")] #[cfg(feature = "filesystem_watcher")]
app.add_system_to_stage( app.add_system_to_stage(stage::LOAD_ASSETS, io::filesystem_watcher_system.system());
stage::LOAD_ASSETS,
asset_server::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 anyhow::Result;
use bevy_ecs::{Res, ResMut, Resource}; use bevy_ecs::{Res, ResMut, Resource};
use crossbeam_channel::{Receiver, Sender, TryRecvError}; use bevy_type_registry::{TypeUuid, TypeUuidDynamic};
use fs::File; use bevy_utils::HashMap;
use io::Read; use crossbeam_channel::{Receiver, Sender};
use std::{ use downcast_rs::{impl_downcast, Downcast};
fs, io, use std::path::Path;
path::{Path, PathBuf},
};
use thiserror::Error;
/// Errors that occur while loading assets /// A loader for an asset source
#[derive(Error, Debug)] pub trait AssetLoader: Send + Sync + 'static {
pub enum AssetLoadError { fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<(), anyhow::Error>;
#[error("Encountered an io error while loading asset.")] fn extensions(&self) -> &[&str];
Io(#[from] io::Error),
#[error("This asset's loader encountered an error while loading.")]
LoaderError(#[from] anyhow::Error),
} }
/// A loader for a given asset of type `T` pub trait Asset: TypeUuid + AssetDynamic {}
pub trait AssetLoader<T>: Send + Sync + 'static {
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<T, anyhow::Error>; pub trait AssetDynamic: Downcast + TypeUuidDynamic + Send + Sync + 'static {}
fn extensions(&self) -> &[&str]; impl_downcast!(AssetDynamic);
fn load_from_file(&self, asset_path: &Path) -> Result<T, AssetLoadError> {
let mut file = File::open(asset_path)?; impl<T> Asset for T where T: TypeUuid + AssetDynamic + TypeUuidDynamic {}
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?; impl<T> AssetDynamic for T where T: Send + Sync + 'static + TypeUuidDynamic {}
let asset = self.from_bytes(asset_path, bytes)?;
Ok(asset) 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` /// The result of loading an asset of type `T`
#[derive(Debug)] #[derive(Debug)]
pub struct AssetResult<T: 'static> { pub struct AssetResult<T: Resource> {
pub result: Result<T, AssetLoadError>, pub asset: T,
pub handle: Handle<T>, pub id: HandleId,
pub path: PathBuf, pub version: usize,
pub version: AssetVersion,
} }
/// A channel to send and receive [AssetResult]s /// A channel to send and receive [AssetResult]s
#[derive(Debug)] #[derive(Debug)]
pub struct AssetChannel<T: 'static> { pub struct AssetLifecycleChannel<T: Resource> {
pub sender: Sender<AssetResult<T>>, pub sender: Sender<AssetLifecycleEvent<T>>,
pub receiver: Receiver<AssetResult<T>>, pub receiver: Receiver<AssetLifecycleEvent<T>>,
} }
impl<T> AssetChannel<T> { pub enum AssetLifecycleEvent<T: Resource> {
#[allow(clippy::new_without_default)] Create(AssetResult<T>),
pub fn new() -> Self { 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(); 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 /// Reads [AssetResult]s from an [AssetChannel] and updates the [Assets] collection and [LoadState] accordingly
pub fn update_asset_storage_system<T: Resource>( pub fn update_asset_storage_system<T: Asset + AssetDynamic>(
asset_channel: Res<AssetChannel<T>>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut assets: ResMut<Assets<T>>, mut assets: ResMut<Assets<T>>,
) { ) {
loop { asset_server.update_asset_storage(&mut assets);
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"),
}
}
} }

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_app = { path = "../bevy_app", version = "0.2.1" }
bevy_asset = { path = "../bevy_asset", version = "0.2.1" } bevy_asset = { path = "../bevy_asset", version = "0.2.1" }
bevy_ecs = { path = "../bevy_ecs", 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 # other
anyhow = "1.0" anyhow = "1.0"

View file

@ -1,5 +1,5 @@
use crate::{AudioSource, Decodable}; use crate::{AudioSource, Decodable};
use bevy_asset::{Assets, Handle}; use bevy_asset::{Asset, Assets, Handle};
use bevy_ecs::Res; use bevy_ecs::Res;
use parking_lot::RwLock; use parking_lot::RwLock;
use rodio::{Device, Sink}; use rodio::{Device, Sink};
@ -39,7 +39,7 @@ where
impl<P> AudioOutput<P> impl<P> AudioOutput<P>
where where
P: Decodable, P: Asset + Decodable,
<P as Decodable>::Decoder: rodio::Source + Send + Sync, <P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + 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 /// 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>>) pub fn play_queued_audio_system<P: Asset>(
where audio_sources: Res<Assets<P>>,
audio_output: Res<AudioOutput<P>>,
) where
P: Decodable, P: Decodable,
<P as Decodable>::Decoder: rodio::Source + Send + Sync, <P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, <<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,

View file

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

View file

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

View file

@ -21,3 +21,4 @@ proc-macro-crate = "0.1.5"
proc-macro2 = "1.0" proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
syn = "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 render_resources;
mod resource; mod resource;
mod shader_defs; mod shader_defs;
mod type_uuid;
use proc_macro::TokenStream; 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 { pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream {
app_plugin::derive_dynamic_plugin(input) 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_asset: String,
pub bevy_core: String, pub bevy_core: String,
pub bevy_app: String, pub bevy_app: String,
pub bevy_type_registry: String,
} }
impl Modules { impl Modules {
@ -17,6 +18,7 @@ impl Modules {
bevy_render: "bevy::render".to_string(), bevy_render: "bevy::render".to_string(),
bevy_core: "bevy::core".to_string(), bevy_core: "bevy::core".to_string(),
bevy_app: "bevy::app".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_render: "bevy_render".to_string(),
bevy_core: "bevy_core".to_string(), bevy_core: "bevy_core".to_string(),
bevy_app: "bevy_app".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; use #bevy_core_path::Bytes;
Some(self.byte_len()) 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 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_tasks = { path = "../bevy_tasks", version = "0.2.1" }
bevy_utils = { path = "../bevy_utils", version = "0.2.1" } bevy_utils = { path = "../bevy_utils", version = "0.2.1" }
rand = "0.7.3" rand = "0.7.3"
thiserror = "1.0"
fixedbitset = "0.3.1" fixedbitset = "0.3.1"
downcast-rs = "1.2.0" downcast-rs = "1.2.0"
parking_lot = "0.11.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 /// 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 /// cloneable (like a task pool) without taking a borrow on the resource map
pub fn get_cloned<T: Resource + Clone>(&self) -> Option<T> { 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 crate::resource::{Resource, Resources};
use bevy_hecs::{Bundle, Component, DynamicBundle, Entity, EntityReserver, World}; use bevy_hecs::{Bundle, Component, DynamicBundle, Entity, EntityReserver, World};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{fmt, marker::PhantomData, sync::Arc}; use std::{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(),
}
}
}
/// A [World] mutation /// A [World] mutation
pub trait WorldWriter: Send + Sync { pub trait Command: Send + Sync {
fn write(self: Box<Self>, world: &mut World); fn write(self: Box<Self>, world: &mut World, resources: &mut Resources);
} }
#[derive(Debug)] #[derive(Debug)]
@ -38,11 +17,11 @@ where
components: T, components: T,
} }
impl<T> WorldWriter for Spawn<T> impl<T> Command for Spawn<T>
where where
T: DynamicBundle + Send + Sync + 'static, 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); world.spawn(self.components);
} }
} }
@ -55,12 +34,12 @@ where
components_iter: I, components_iter: I,
} }
impl<I> WorldWriter for SpawnBatch<I> impl<I> Command for SpawnBatch<I>
where where
I: IntoIterator + Send + Sync, I: IntoIterator + Send + Sync,
I::Item: Bundle, 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); world.spawn_batch(self.components_iter);
} }
} }
@ -70,8 +49,8 @@ pub(crate) struct Despawn {
entity: Entity, entity: Entity,
} }
impl WorldWriter for Despawn { impl Command for Despawn {
fn write(self: Box<Self>, world: &mut World) { fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
if let Err(e) = world.despawn(self.entity) { if let Err(e) = world.despawn(self.entity) {
log::debug!("Failed to despawn entity {:?}: {}", self.entity, e); log::debug!("Failed to despawn entity {:?}: {}", self.entity, e);
} }
@ -86,11 +65,11 @@ where
components: T, components: T,
} }
impl<T> WorldWriter for Insert<T> impl<T> Command for Insert<T>
where where
T: DynamicBundle + Send + Sync + 'static, 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(); world.insert(self.entity, self.components).unwrap();
} }
} }
@ -104,11 +83,11 @@ where
component: T, component: T,
} }
impl<T> WorldWriter for InsertOne<T> impl<T> Command for InsertOne<T>
where where
T: Component, 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(); world.insert(self.entity, (self.component,)).unwrap();
} }
} }
@ -122,11 +101,11 @@ where
phantom: PhantomData<T>, phantom: PhantomData<T>,
} }
impl<T> WorldWriter for RemoveOne<T> impl<T> Command for RemoveOne<T>
where where
T: Component, 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() { if world.get::<T>(self.entity).is_ok() {
world.remove_one::<T>(self.entity).unwrap(); world.remove_one::<T>(self.entity).unwrap();
} }
@ -142,11 +121,11 @@ where
phantom: PhantomData<T>, phantom: PhantomData<T>,
} }
impl<T> WorldWriter for Remove<T> impl<T> Command for Remove<T>
where where
T: Bundle + Send + Sync + 'static, 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(); world.remove::<T>(self.entity).unwrap();
} }
} }
@ -159,8 +138,8 @@ pub struct InsertResource<T: Resource> {
resource: T, resource: T,
} }
impl<T: Resource> ResourcesWriter for InsertResource<T> { impl<T: Resource> Command for InsertResource<T> {
fn write(self: Box<Self>, resources: &mut Resources) { fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
resources.insert(self.resource); resources.insert(self.resource);
} }
} }
@ -171,15 +150,15 @@ pub(crate) struct InsertLocalResource<T: Resource> {
system_id: SystemId, system_id: SystemId,
} }
impl<T: Resource> ResourcesWriter for InsertLocalResource<T> { impl<T: Resource> Command for InsertLocalResource<T> {
fn write(self: Box<Self>, resources: &mut Resources) { fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
resources.insert_local(self.system_id, self.resource); resources.insert_local(self.system_id, self.resource);
} }
} }
#[derive(Debug, Default)] #[derive(Default)]
pub struct CommandsInternal { pub struct CommandsInternal {
pub commands: Vec<Command>, pub commands: Vec<Box<dyn Command>>,
pub current_entity: Option<Entity>, pub current_entity: Option<Entity>,
pub entity_reserver: Option<EntityReserver>, pub entity_reserver: Option<EntityReserver>,
} }
@ -192,8 +171,7 @@ impl CommandsInternal {
.expect("entity reserver has not been set") .expect("entity reserver has not been set")
.reserve_entity(); .reserve_entity();
self.current_entity = Some(entity); self.current_entity = Some(entity);
self.commands self.commands.push(Box::new(Insert { entity, components }));
.push(Command::WriteWorld(Box::new(Insert { entity, components })));
self self
} }
@ -202,43 +180,35 @@ impl CommandsInternal {
components: impl DynamicBundle + Send + Sync + 'static, components: impl DynamicBundle + Send + Sync + 'static,
) -> &mut Self { ) -> &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."); 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, entity: current_entity,
components, components,
}))); }));
self self
} }
pub fn with(&mut self, component: impl Component) -> &mut 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."); 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, entity: current_entity,
component, component,
}))); }));
self self
} }
pub fn write_world<W: WorldWriter + 'static>(&mut self, world_writer: W) -> &mut Self { pub fn add_command<C: Command + 'static>(&mut self, command: C) -> &mut Self {
self.write_world_boxed(Box::new(world_writer)) self.commands.push(Box::new(command));
}
pub fn write_world_boxed(&mut self, world_writer: Box<dyn WorldWriter + 'static>) -> &mut Self {
self.commands.push(Command::WriteWorld(world_writer));
self self
} }
pub fn write_resources<W: ResourcesWriter + 'static>( pub fn add_command_boxed(&mut self, command: Box<dyn Command>) -> &mut Self {
&mut self, self.commands.push(command);
resources_writer: W,
) -> &mut Self {
self.commands
.push(Command::WriteResources(Box::new(resources_writer)));
self self
} }
} }
/// A queue of [Command]s to run on the current [World] and [Resources] /// A queue of [Command]s to run on the current [World] and [Resources]
#[derive(Debug, Default, Clone)] #[derive(Default, Clone)]
pub struct Commands { pub struct Commands {
pub commands: Arc<Mutex<CommandsInternal>>, pub commands: Arc<Mutex<CommandsInternal>>,
} }
@ -257,12 +227,12 @@ impl Commands {
I: IntoIterator + Send + Sync + 'static, I: IntoIterator + Send + Sync + 'static,
I::Item: Bundle, I::Item: Bundle,
{ {
self.write_world(SpawnBatch { components_iter }) self.add_command(SpawnBatch { components_iter })
} }
/// Despawns only the specified entity, ignoring any other consideration. /// Despawns only the specified entity, ignoring any other consideration.
pub fn despawn(&mut self, entity: Entity) -> &mut Self { 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 { pub fn with(&mut self, component: impl Component) -> &mut Self {
@ -289,15 +259,15 @@ impl Commands {
entity: Entity, entity: Entity,
components: impl DynamicBundle + Send + Sync + 'static, components: impl DynamicBundle + Send + Sync + 'static,
) -> &mut Self { ) -> &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 { 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 { 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>( pub fn insert_local_resource<T: Resource>(
@ -305,39 +275,26 @@ impl Commands {
system_id: SystemId, system_id: SystemId,
resource: T, resource: T,
) -> &mut Self { ) -> &mut Self {
self.write_resources(InsertLocalResource { self.add_command(InsertLocalResource {
system_id, system_id,
resource, resource,
}) })
} }
pub fn write_world<W: WorldWriter + 'static>(&mut self, world_writer: W) -> &mut Self { pub fn add_command<C: Command + 'static>(&mut self, command: C) -> &mut Self {
self.commands.lock().write_world(world_writer); self.commands.lock().add_command(command);
self self
} }
pub fn write_world_boxed(&mut self, world_writer: Box<dyn WorldWriter + 'static>) -> &mut Self { pub fn add_command_boxed(&mut self, command: Box<dyn Command>) -> &mut Self {
self.commands.lock().write_world_boxed(world_writer); self.commands.lock().add_command_boxed(command);
self
}
pub fn write_resources<W: ResourcesWriter + 'static>(
&mut self,
resources_writer: W,
) -> &mut Self {
self.commands.lock().write_resources(resources_writer);
self self
} }
pub fn apply(&self, world: &mut World, resources: &mut Resources) { pub fn apply(&self, world: &mut World, resources: &mut Resources) {
let mut commands = self.commands.lock(); let mut commands = self.commands.lock();
for command in commands.commands.drain(..) { for command in commands.commands.drain(..) {
match command { command.write(world, resources);
Command::WriteWorld(writer) => {
writer.write(world);
}
Command::WriteResources(writer) => writer.write(resources),
}
} }
} }
@ -361,7 +318,7 @@ impl Commands {
where where
T: Component, T: Component,
{ {
self.write_world(RemoveOne::<T> { self.add_command(RemoveOne::<T> {
entity, entity,
phantom: PhantomData, phantom: PhantomData,
}) })
@ -371,7 +328,7 @@ impl Commands {
where where
T: Bundle + Send + Sync + 'static, T: Bundle + Send + Sync + 'static,
{ {
self.write_world(Remove::<T> { self.add_command(Remove::<T> {
entity, entity,
phantom: PhantomData, 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; mod world_builder;
pub use entity_map::*;
pub use world_builder::*; pub use world_builder::*;

View file

@ -16,10 +16,17 @@ keywords = ["bevy"]
# bevy # bevy
bevy_app = { path = "../bevy_app", version = "0.2.1" } bevy_app = { path = "../bevy_app", version = "0.2.1" }
bevy_asset = { path = "../bevy_asset", 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_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 # other
gltf = { version = "0.15.2", default-features = false, features = ["utils"] } gltf = { version = "0.15.2", default-features = false, features = ["utils"] }
image = { version = "0.23", default-features = false }
thiserror = "1.0" thiserror = "1.0"
anyhow = "1.0" anyhow = "1.0"
base64 = "0.12.3" base64 = "0.12.3"

View file

@ -3,7 +3,6 @@ pub use loader::*;
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::AddAsset; use bevy_asset::AddAsset;
use bevy_render::mesh::Mesh;
/// Adds support for GLTF file loading to Apps /// Adds support for GLTF file loading to Apps
#[derive(Default)] #[derive(Default)]
@ -11,6 +10,6 @@ pub struct GltfPlugin;
impl Plugin for GltfPlugin { impl Plugin for GltfPlugin {
fn build(&self, app: &mut AppBuilder) { 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::{ use bevy_render::{
mesh::{Indices, Mesh, VertexAttribute}, mesh::{Indices, Mesh, VertexAttribute},
pipeline::PrimitiveTopology, pipeline::PrimitiveTopology,
prelude::{Color, Texture},
texture::TextureFormat,
}; };
use bevy_scene::Scene;
use anyhow::Result; use bevy_transform::{
use bevy_asset::AssetLoader; hierarchy::{BuildWorldChildren, WorldChildBuilder},
use gltf::{buffer::Source, mesh::Mode}; prelude::{GlobalTransform, Transform},
use std::{fs, io, path::Path}; };
use gltf::{mesh::Mode, Primitive};
use image::{GenericImageView, ImageFormat};
use std::path::Path;
use thiserror::Error; 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 /// An error that occurs when loading a GLTF file
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum GltfError { pub enum GltfError {
@ -34,14 +26,234 @@ pub enum GltfError {
UnsupportedPrimitive { mode: Mode }, UnsupportedPrimitive { mode: Mode },
#[error("Invalid GLTF file.")] #[error("Invalid GLTF file.")]
Gltf(#[from] gltf::Error), Gltf(#[from] gltf::Error),
#[error("Failed to load file.")]
Io(#[from] io::Error),
#[error("Binary blob is missing.")] #[error("Binary blob is missing.")]
MissingBlob, MissingBlob,
#[error("Failed to decode base64 mesh data.")] #[error("Failed to decode base64 mesh data.")]
Base64Decode(#[from] base64::DecodeError), Base64Decode(#[from] base64::DecodeError),
#[error("Unsupported buffer format.")] #[error("Unsupported buffer format.")]
BufferFormatUnsupported, 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> { 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 fn load_buffers(
pub fn load_gltf(asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh, GltfError> { gltf: &gltf::Gltf,
let gltf = gltf::Gltf::from_slice(&bytes)?; load_context: &LoadContext,
let buffer_data = load_buffers(&gltf, asset_path)?; asset_path: &Path,
for scene in gltf.scenes() { ) -> Result<Vec<Vec<u8>>, GltfError> {
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> {
const OCTET_STREAM_URI: &str = "data:application/octet-stream;base64,"; const OCTET_STREAM_URI: &str = "data:application/octet-stream;base64,";
let mut buffer_data = Vec::new(); let mut buffer_data = Vec::new();
for buffer in gltf.buffers() { for buffer in gltf.buffers() {
match buffer.source() { match buffer.source() {
Source::Uri(uri) => { gltf::buffer::Source::Uri(uri) => {
if uri.starts_with("data:") { if uri.starts_with("data:") {
if uri.starts_with(OCTET_STREAM_URI) { if uri.starts_with(OCTET_STREAM_URI) {
buffer_data.push(base64::decode(&uri[OCTET_STREAM_URI.len()..])?); 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); return Err(GltfError::BufferFormatUnsupported);
} }
} else { } else {
// TODO: Remove this and add dep
let buffer_path = asset_path.parent().unwrap().join(uri); 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); buffer_data.push(buffer_bytes);
} }
} }
Source::Bin => { gltf::buffer::Source::Bin => {
if let Some(blob) = gltf.blob.as_deref() { if let Some(blob) = gltf.blob.as_deref() {
buffer_data.push(blob.into()); buffer_data.push(blob.into());
} else { } else {

View file

@ -13,9 +13,9 @@ pub mod prelude {
} }
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::AddAsset; use bevy_asset::{AddAsset, Assets, Handle};
use bevy_ecs::IntoQuerySystem; 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 bevy_type_registry::RegisterType;
use light::Light; use light::Light;
use material::StandardMaterial; use material::StandardMaterial;
@ -36,5 +36,19 @@ impl Plugin for PbrPlugin {
let resources = app.resources(); let resources = app.resources();
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap(); let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
add_pbr_graph(&mut render_graph, resources); 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_asset::{self, Handle};
use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture}; 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 /// A material with "standard" properties used in PBR lighting
#[derive(Debug, RenderResources, ShaderDefs)] #[derive(Debug, RenderResources, ShaderDefs, TypeUuid)]
#[allow(clippy::manual_non_exhaustive)] #[uuid = "dace545e-4bc6-4595-a79d-c224fc694975"]
pub struct StandardMaterial { pub struct StandardMaterial {
pub albedo: Color, pub albedo: Color,
#[shader_def] #[shader_def]
@ -11,12 +12,6 @@ pub struct StandardMaterial {
#[render_resources(ignore)] #[render_resources(ignore)]
#[shader_def] #[shader_def]
pub shaded: bool, 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 { impl Default for StandardMaterial {
@ -25,7 +20,6 @@ impl Default for StandardMaterial {
albedo: Color::rgb(1.0, 1.0, 1.0), albedo: Color::rgb(1.0, 1.0, 1.0),
albedo_texture: None, albedo_texture: None,
shaded: true, shaded: true,
__non_exhaustive: (),
} }
} }
} }

View file

@ -8,9 +8,10 @@ use bevy_render::{
shader::{Shader, ShaderStage, ShaderStages}, shader::{Shader, ShaderStage, ShaderStages},
texture::TextureFormat, texture::TextureFormat,
}; };
use bevy_type_registry::TypeUuid;
pub const FORWARD_PIPELINE_HANDLE: Handle<PipelineDescriptor> = 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 { pub(crate) fn build_forward_pipeline(shaders: &mut Assets<Shader>) -> PipelineDescriptor {
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)); graph.add_system_node(node::LIGHTS, LightsNode::new(10));
let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap(); let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap();
let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap(); let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
pipelines.set( pipelines.set_untracked(
FORWARD_PIPELINE_HANDLE, FORWARD_PIPELINE_HANDLE,
build_forward_pipeline(&mut shaders), 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 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 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 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 RED: Color = Color::rgb_linear(1.0, 0.0, 0.0);
pub const WHITE: Color = Color::rgb_linear(1.0, 1.0, 1.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 { impl Into<[f32; 4]> for Color {
fn into(self) -> [f32; 4] { fn into(self) -> [f32; 4] {
[self.red, self.green, self.blue, self.alpha] [self.red, self.green, self.blue, self.alpha]
} }
} }
impl Mul<f32> for Color { impl Mul<f32> for Color {
type Output = Color; type Output = Color;

View file

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

View file

@ -1,4 +1,3 @@
pub mod batch;
pub mod camera; pub mod camera;
pub mod color; pub mod color;
pub mod colorspace; pub mod colorspace;
@ -12,6 +11,7 @@ pub mod renderer;
pub mod shader; pub mod shader;
pub mod texture; pub mod texture;
use bevy_type_registry::RegisterType;
pub use once_cell; pub use once_cell;
pub mod prelude { pub mod prelude {
@ -33,7 +33,6 @@ use base::{MainPass, Msaa};
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::AddAsset; use bevy_asset::AddAsset;
use bevy_ecs::{IntoQuerySystem, IntoThreadLocalSystem}; use bevy_ecs::{IntoQuerySystem, IntoThreadLocalSystem};
use bevy_type_registry::RegisterType;
use camera::{ use camera::{
ActiveCameras, Camera, OrthographicProjection, PerspectiveProjection, VisibleEntities, ActiveCameras, Camera, OrthographicProjection, PerspectiveProjection, VisibleEntities,
}; };
@ -83,11 +82,11 @@ impl Plugin for RenderPlugin {
fn build(&self, app: &mut AppBuilder) { fn build(&self, app: &mut AppBuilder) {
#[cfg(feature = "png")] #[cfg(feature = "png")]
{ {
app.add_asset_loader::<Texture, ImageTextureLoader>(); app.init_asset_loader::<ImageTextureLoader>();
} }
#[cfg(feature = "hdr")] #[cfg(feature = "hdr")]
{ {
app.add_asset_loader::<Texture, HdrTextureLoader>(); app.init_asset_loader::<HdrTextureLoader>();
} }
if app.resources().get::<ClearColor>().is_none() { 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_core::AsBytes;
use bevy_ecs::{Local, Query, Res, ResMut}; use bevy_ecs::{Local, Query, Res, ResMut};
use bevy_math::*; use bevy_math::*;
use bevy_type_registry::TypeUuid;
use bevy_utils::HashSet; use bevy_utils::HashSet;
use std::borrow::Cow; use std::borrow::Cow;
use thiserror::Error; use thiserror::Error;
@ -112,7 +113,8 @@ pub enum Indices {
U32(Vec<u32>), U32(Vec<u32>),
} }
#[derive(Debug)] #[derive(Debug, TypeUuid)]
#[uuid = "8ecbac0f-f545-4473-ad43-e1f4243af51e"]
pub struct Mesh { pub struct Mesh {
pub primitive_topology: PrimitiveTopology, pub primitive_topology: PrimitiveTopology,
pub attributes: Vec<VertexAttribute>, pub attributes: Vec<VertexAttribute>,
@ -476,10 +478,10 @@ pub mod shape {
fn remove_current_mesh_resources( fn remove_current_mesh_resources(
render_resource_context: &dyn RenderResourceContext, render_resource_context: &dyn RenderResourceContext,
handle: Handle<Mesh>, handle: &Handle<Mesh>,
) { ) {
if let Some(RenderResourceId::Buffer(buffer)) = 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_buffer(buffer);
render_resource_context.remove_asset_resource(handle, VERTEX_BUFFER_ASSET_INDEX); 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; let render_resource_context = &**render_resource_context;
for event in state.mesh_event_reader.iter(&mesh_events) { for event in state.mesh_event_reader.iter(&mesh_events) {
match event { match event {
AssetEvent::Created { handle } => { AssetEvent::Created { ref handle } => {
changed_meshes.insert(*handle); changed_meshes.insert(handle.clone_weak());
} }
AssetEvent::Modified { handle } => { AssetEvent::Modified { ref handle } => {
changed_meshes.insert(*handle); changed_meshes.insert(handle.clone_weak());
remove_current_mesh_resources(render_resource_context, *handle); remove_current_mesh_resources(render_resource_context, handle);
} }
AssetEvent::Removed { handle } => { AssetEvent::Removed { ref handle } => {
remove_current_mesh_resources(render_resource_context, *handle); remove_current_mesh_resources(render_resource_context, handle);
// if mesh was modified and removed in the same update, ignore the modification // if mesh was modified and removed in the same update, ignore the modification
// events are ordered so future modification events are ok // events are ordered so future modification events are ok
changed_meshes.remove(handle); changed_meshes.remove(handle);
@ -560,12 +562,12 @@ pub fn mesh_resource_provider_system(
); );
render_resource_context.set_asset_resource( render_resource_context.set_asset_resource(
*changed_mesh_handle, changed_mesh_handle,
RenderResourceId::Buffer(vertex_buffer), RenderResourceId::Buffer(vertex_buffer),
VERTEX_BUFFER_ASSET_INDEX, VERTEX_BUFFER_ASSET_INDEX,
); );
render_resource_context.set_asset_resource( render_resource_context.set_asset_resource(
*changed_mesh_handle, changed_mesh_handle,
RenderResourceId::Buffer(index_buffer), RenderResourceId::Buffer(index_buffer),
INDEX_BUFFER_ASSET_INDEX, 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 // TODO: remove this once batches are pipeline specific and deprecate assigned_meshes draw target
for (handle, mut render_pipelines) in &mut query.iter() { 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() { for render_pipeline in render_pipelines.pipelines.iter_mut() {
render_pipeline.specialization.primitive_topology = mesh.primitive_topology; render_pipeline.specialization.primitive_topology = mesh.primitive_topology;
} }
} }
if let Some(RenderResourceId::Buffer(vertex_buffer)) = 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( render_pipelines.bindings.set_vertex_buffer(
"Vertex", "Vertex",
vertex_buffer, vertex_buffer,
render_resource_context render_resource_context
.get_asset_resource(*handle, INDEX_BUFFER_ASSET_INDEX) .get_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX)
.and_then(|r| { .and_then(|r| {
if let RenderResourceId::Buffer(buffer) = r { if let RenderResourceId::Buffer(buffer) = r {
Some(buffer) Some(buffer)

View file

@ -9,7 +9,7 @@ pub trait RenderPass {
fn get_render_context(&self) -> &dyn RenderContext; fn get_render_context(&self) -> &dyn RenderContext;
fn set_index_buffer(&mut self, buffer: BufferId, offset: u64); 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_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_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 set_stencil_reference(&mut self, reference: u32);
fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>); fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>);

View file

@ -11,8 +11,10 @@ use crate::{
texture::TextureFormat, texture::TextureFormat,
}; };
use bevy_asset::Assets; 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 struct PipelineDescriptor {
pub name: Option<String>, pub name: Option<String>,
pub layout: Option<PipelineLayout>, pub layout: Option<PipelineLayout>,
@ -137,7 +139,7 @@ impl PipelineDescriptor {
.shader_stages .shader_stages
.fragment .fragment
.as_ref() .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()]; let mut layouts = vec![vertex_spirv.reflect_layout(bevy_conventions).unwrap()];
if let Some(ref fragment_spirv) = fragment_spirv { if let Some(ref fragment_spirv) = fragment_spirv {

View file

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

View file

@ -56,7 +56,7 @@ impl RenderPipelines {
RenderPipelines { RenderPipelines {
pipelines: handles pipelines: handles
.into_iter() .into_iter()
.map(|pipeline| RenderPipeline::new(*pipeline)) .map(|pipeline| RenderPipeline::new(pipeline.clone_weak()))
.collect::<Vec<RenderPipeline>>(), .collect::<Vec<RenderPipeline>>(),
..Default::default() ..Default::default()
} }
@ -84,7 +84,13 @@ pub fn draw_render_pipelines_system(
continue; 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() { let (index_range, index_format) = match mesh.indices.as_ref() {
Some(Indices::U32(indices)) => (Some(0..indices.len() as u32), IndexFormat::Uint32), Some(Indices::U32(indices)) => (Some(0..indices.len() as u32), IndexFormat::Uint32),
Some(Indices::U16(indices)) => (Some(0..indices.len() as u32), IndexFormat::Uint16), Some(Indices::U16(indices)) => (Some(0..indices.len() as u32), IndexFormat::Uint16),
@ -101,7 +107,7 @@ pub fn draw_render_pipelines_system(
draw_context draw_context
.set_pipeline( .set_pipeline(
&mut draw, &mut draw,
render_pipeline.pipeline, &render_pipeline.pipeline,
&render_pipeline.specialization, &render_pipeline.specialization,
) )
.unwrap(); .unwrap();

View file

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

View file

@ -9,7 +9,7 @@ use crate::{
texture, texture,
}; };
use bevy_asset::{Assets, Handle}; use bevy_asset::{Asset, Assets, Handle, HandleId};
use bevy_ecs::{ use bevy_ecs::{
Commands, Entity, IntoQuerySystem, Local, Query, Res, ResMut, Resources, System, World, 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> impl<T> SystemNode for AssetRenderResourcesNode<T>
where where
T: renderer::RenderResources, T: renderer::RenderResources + Asset,
{ {
fn get_system(&self, commands: &mut Commands) -> Box<dyn System> { fn get_system(&self, commands: &mut Commands) -> Box<dyn System> {
let system = asset_render_resources_node_system::<T>.system(); let system = asset_render_resources_node_system::<T>.system();
@ -555,7 +555,7 @@ where
system.id(), system.id(),
RenderResourcesNodeState { RenderResourcesNodeState {
command_queue: self.command_queue.clone(), 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, dynamic_uniforms: self.dynamic_uniforms,
}, },
); );
@ -564,8 +564,8 @@ where
} }
} }
fn asset_render_resources_node_system<T: RenderResources>( fn asset_render_resources_node_system<T: RenderResources + Asset>(
mut state: Local<RenderResourcesNodeState<Handle<T>, T>>, mut state: Local<RenderResourcesNodeState<HandleId, T>>,
assets: Res<Assets<T>>, assets: Res<Assets<T>>,
mut asset_render_resource_bindings: ResMut<AssetRenderResourceBindings>, mut asset_render_resource_bindings: ResMut<AssetRenderResourceBindings>,
render_resource_context: Res<Box<dyn RenderResourceContext>>, 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 uniform_buffer_arrays = &mut state.uniform_buffer_arrays;
let render_resource_context = &**render_resource_context; let render_resource_context = &**render_resource_context;
let modified_assets = assets let modified_assets = assets.ids().collect::<Vec<_>>();
.iter()
.map(|(handle, _)| handle)
.collect::<Vec<Handle<T>>>();
uniform_buffer_arrays.begin_update(); uniform_buffer_arrays.begin_update();
// initialize uniform buffer arrays using the first RenderResources // initialize uniform buffer arrays using the first RenderResources
if let Some(first_handle) = modified_assets.get(0) { 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); uniform_buffer_arrays.initialize(asset);
} }
for asset_handle in modified_assets.iter() { 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); 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); 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, 0..state.uniform_buffer_arrays.staging_buffer_size as u64,
&mut |mut staging_buffer, _render_resource_context| { &mut |mut staging_buffer, _render_resource_context| {
for asset_handle in modified_assets.iter() { 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 = let mut render_resource_bindings = asset_render_resource_bindings
asset_render_resource_bindings.get_or_insert_mut(*asset_handle); .get_or_insert_mut(&Handle::<T>::weak(*asset_handle));
// TODO: only setup buffer if we haven't seen this handle before // TODO: only setup buffer if we haven't seen this handle before
state.uniform_buffer_arrays.write_uniform_buffers( state.uniform_buffer_arrays.write_uniform_buffers(
*asset_handle, *asset_handle,
@ -627,9 +625,9 @@ fn asset_render_resources_node_system<T: RenderResources>(
} else { } else {
let mut staging_buffer: [u8; 0] = []; let mut staging_buffer: [u8; 0] = [];
for asset_handle in modified_assets.iter() { 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 = 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 // TODO: only setup buffer if we haven't seen this handle before
state.uniform_buffer_arrays.write_uniform_buffers( state.uniform_buffer_arrays.write_uniform_buffers(
*asset_handle, *asset_handle,
@ -646,7 +644,7 @@ fn asset_render_resources_node_system<T: RenderResources>(
if !draw.is_visible { if !draw.is_visible {
continue; 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); 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) { for event in self.texture_event_reader.iter(&texture_events) {
match event { match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { 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 texture_descriptor: TextureDescriptor = texture.into();
let width = texture.size.x() as usize; let width = texture.size.x() as usize;
let aligned_width = get_aligned(texture.size.x()); let aligned_width = get_aligned(texture.size.x());
@ -57,7 +57,7 @@ impl Node for TextureCopyNode {
let texture_resource = render_context let texture_resource = render_context
.resources() .resources()
.get_asset_resource(*handle, TEXTURE_ASSET_INDEX) .get_asset_resource(handle, TEXTURE_ASSET_INDEX)
.unwrap(); .unwrap();
render_context.copy_buffer_to_texture( render_context.copy_buffer_to_texture(

View file

@ -76,7 +76,7 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
buffer 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) { fn remove_buffer(&self, buffer: BufferId) {
self.buffer_info.write().remove(&buffer); 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) { fn remove_asset_resource_untyped(&self, handle: HandleUntyped, index: usize) {
self.asset_resources.write().remove(&(handle, index)); 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 write_buffer_bytes(&self, buffer: &mut [u8]);
fn buffer_byte_len(&self) -> Option<usize>; fn buffer_byte_len(&self) -> Option<usize>;
// TODO: consider making these panic by default, but return non-options // 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 { pub trait RenderResources: Send + Sync + 'static {
@ -136,7 +136,7 @@ macro_rules! impl_render_resource_bytes {
Some(self.byte_len()) Some(self.byte_len())
} }
fn texture(&self) -> Option<Handle<Texture>> { fn texture(&self) -> Option<&Handle<Texture>> {
None None
} }
} }
@ -175,7 +175,7 @@ where
Some(self.byte_len()) Some(self.byte_len())
} }
fn texture(&self) -> Option<Handle<Texture>> { fn texture(&self) -> Option<&Handle<Texture>> {
None None
} }
} }
@ -194,7 +194,7 @@ impl RenderResource for GlobalTransform {
Some(std::mem::size_of::<[f32; 16]>()) Some(std::mem::size_of::<[f32; 16]>())
} }
fn texture(&self) -> Option<Handle<Texture>> { fn texture(&self) -> Option<&Handle<Texture>> {
None None
} }
} }

View file

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

View file

@ -4,7 +4,7 @@ use crate::{
shader::Shader, shader::Shader,
texture::{SamplerDescriptor, TextureDescriptor}, texture::{SamplerDescriptor, TextureDescriptor},
}; };
use bevy_asset::{Assets, Handle, HandleUntyped}; use bevy_asset::{Asset, Assets, Handle, HandleUntyped};
use bevy_window::Window; use bevy_window::Window;
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use std::ops::Range; use std::ops::Range;
@ -27,8 +27,8 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
fn map_buffer(&self, id: BufferId); fn map_buffer(&self, id: BufferId);
fn unmap_buffer(&self, id: BufferId); fn unmap_buffer(&self, id: BufferId);
fn create_buffer_with_data(&self, buffer_info: BufferInfo, data: &[u8]) -> 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(&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_from_source(&self, shader_handle: &Handle<Shader>, shader: &Shader);
fn remove_buffer(&self, buffer: BufferId); fn remove_buffer(&self, buffer: BufferId);
fn remove_texture(&self, texture: TextureId); fn remove_texture(&self, texture: TextureId);
fn remove_sampler(&self, sampler: SamplerId); fn remove_sampler(&self, sampler: SamplerId);
@ -63,25 +63,33 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
} }
impl dyn RenderResourceContext { impl dyn RenderResourceContext {
pub fn set_asset_resource<T>(&self, handle: Handle<T>, resource: RenderResourceId, index: usize) pub fn set_asset_resource<T>(
where &self,
T: 'static, 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 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 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 super::ShaderLayout;
use bevy_asset::Handle; use bevy_asset::Handle;
use bevy_type_registry::TypeUuid;
use std::marker::Copy; use std::marker::Copy;
/// The stage of a shader /// The stage of a shader
@ -98,7 +99,8 @@ impl ShaderSource {
} }
/// A shader, as defined by its [ShaderSource] and [ShaderStage] /// 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 struct Shader {
pub source: ShaderSource, pub source: ShaderSource,
pub stage: ShaderStage, pub stage: ShaderStage,
@ -164,8 +166,8 @@ impl<'a> Iterator for ShaderStagesIterator<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let ret = match self.state { let ret = match self.state {
0 => Some(self.shader_stages.vertex), 0 => Some(self.shader_stages.vertex.clone_weak()),
1 => self.shader_stages.fragment, 1 => self.shader_stages.fragment.as_ref().map(|h| h.clone_weak()),
_ => None, _ => None,
}; };
self.state += 1; 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}; use crate::{pipeline::RenderPipelines, Texture};
pub use bevy_derive::ShaderDefs; 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 /// 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>>, assets: Res<Assets<T>>,
mut query: Query<(&Handle<T>, &mut RenderPipelines)>, mut query: Query<(&Handle<T>, &mut RenderPipelines)>,
) where ) where
T: ShaderDefs + Send + Sync + 'static, T: ShaderDefs + Send + Sync + 'static,
{ {
for (asset_handle, mut render_pipelines) in &mut query.iter() { 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 shader_def in shader_defs.iter_shader_defs() {
for render_pipeline in render_pipelines.pipelines.iter_mut() { for render_pipeline in render_pipelines.pipelines.iter_mut() {
render_pipeline render_pipeline

View file

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

View file

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

View file

@ -6,12 +6,14 @@ use bevy_app::prelude::{EventReader, Events};
use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_ecs::{Res, ResMut}; use bevy_ecs::{Res, ResMut};
use bevy_math::Vec2; use bevy_math::Vec2;
use bevy_type_registry::TypeUuid;
use bevy_utils::HashSet; use bevy_utils::HashSet;
pub const TEXTURE_ASSET_INDEX: usize = 0; pub const TEXTURE_ASSET_INDEX: usize = 0;
pub const SAMPLER_ASSET_INDEX: usize = 1; pub const SAMPLER_ASSET_INDEX: usize = 1;
#[derive(Debug, Clone)] #[derive(Debug, Clone, TypeUuid)]
#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"]
pub struct Texture { pub struct Texture {
pub data: Vec<u8>, pub data: Vec<u8>,
pub size: Vec2, pub size: Vec2,
@ -82,14 +84,14 @@ impl Texture {
for event in state.event_reader.iter(&texture_events) { for event in state.event_reader.iter(&texture_events) {
match event { match event {
AssetEvent::Created { handle } => { AssetEvent::Created { handle } => {
changed_textures.insert(*handle); changed_textures.insert(handle);
} }
AssetEvent::Modified { handle } => { AssetEvent::Modified { handle } => {
changed_textures.insert(*handle); changed_textures.insert(handle);
Self::remove_current_texture_resources(render_resource_context, *handle); Self::remove_current_texture_resources(render_resource_context, handle);
} }
AssetEvent::Removed { 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 // if texture was modified and removed in the same update, ignore the modification
// events are ordered so future modification events are ok // events are ordered so future modification events are ok
changed_textures.remove(handle); changed_textures.remove(handle);
@ -98,7 +100,7 @@ impl Texture {
} }
for texture_handle in changed_textures.iter() { 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_descriptor: TextureDescriptor = texture.into();
let texture_resource = render_resource_context.create_texture(texture_descriptor); 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); let sampler_resource = render_resource_context.create_sampler(&sampler_descriptor);
render_resource_context.set_asset_resource( render_resource_context.set_asset_resource(
*texture_handle, texture_handle,
RenderResourceId::Texture(texture_resource), RenderResourceId::Texture(texture_resource),
TEXTURE_ASSET_INDEX, TEXTURE_ASSET_INDEX,
); );
render_resource_context.set_asset_resource( render_resource_context.set_asset_resource(
*texture_handle, texture_handle,
RenderResourceId::Sampler(sampler_resource), RenderResourceId::Sampler(sampler_resource),
SAMPLER_ASSET_INDEX, SAMPLER_ASSET_INDEX,
); );
@ -121,7 +123,7 @@ impl Texture {
fn remove_current_texture_resources( fn remove_current_texture_resources(
render_resource_context: &dyn RenderResourceContext, render_resource_context: &dyn RenderResourceContext,
handle: Handle<Texture>, handle: &Handle<Texture>,
) { ) {
if let Some(RenderResourceId::Texture(resource)) = if let Some(RenderResourceId::Texture(resource)) =
render_resource_context.get_asset_resource(handle, TEXTURE_ASSET_INDEX) render_resource_context.get_asset_resource(handle, TEXTURE_ASSET_INDEX)
@ -145,7 +147,7 @@ pub struct TextureResourceSystemState {
impl RenderResource for Option<Handle<Texture>> { impl RenderResource for Option<Handle<Texture>> {
fn resource_type(&self) -> Option<RenderResourceType> { 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]) {} fn write_buffer_bytes(&self, _buffer: &mut [u8]) {}
@ -154,8 +156,8 @@ impl RenderResource for Option<Handle<Texture>> {
None None
} }
fn texture(&self) -> Option<Handle<Texture>> { fn texture(&self) -> Option<&Handle<Texture>> {
*self self.as_ref()
} }
} }
@ -170,7 +172,7 @@ impl RenderResource for Handle<Texture> {
None None
} }
fn texture(&self) -> Option<Handle<Texture>> { fn texture(&self) -> Option<&Handle<Texture>> {
Some(*self) 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;
mod scene_loader;
mod scene_spawner; mod scene_spawner;
pub mod serde; pub mod serde;
pub use loaded_scenes::*; pub use command::*;
pub use dynamic_scene::*;
pub use scene::*; pub use scene::*;
pub use scene_loader::*;
pub use scene_spawner::*; pub use scene_spawner::*;
pub mod prelude { pub mod prelude {
pub use crate::{Scene, SceneSpawner}; pub use crate::{DynamicScene, Scene, SceneSpawner, SpawnSceneCommands};
} }
use bevy_app::prelude::*; use bevy_app::prelude::*;
@ -22,8 +26,9 @@ pub const SCENE_STAGE: &str = "scene";
impl Plugin for ScenePlugin { impl Plugin for ScenePlugin {
fn build(&self, app: &mut AppBuilder) { fn build(&self, app: &mut AppBuilder) {
app.add_asset::<Scene>() app.add_asset::<DynamicScene>()
.add_asset_loader::<Scene, SceneLoader>() .add_asset::<Scene>()
.init_asset_loader::<SceneLoader>()
.init_resource::<SceneSpawner>() .init_resource::<SceneSpawner>()
.add_stage_after(stage::EVENT_UPDATE, SCENE_STAGE) .add_stage_after(stage::EVENT_UPDATE, SCENE_STAGE)
.add_system_to_stage(SCENE_STAGE, scene_spawner_system.thread_local_system()); .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_ecs::World;
use bevy_property::{DynamicProperties, PropertyTypeRegistry}; use bevy_type_registry::TypeUuid;
use bevy_type_registry::ComponentRegistry;
use serde::Serialize;
#[derive(Debug, Default)] #[derive(Debug, TypeUuid)]
#[uuid = "c156503c-edd9-4ec7-8d33-dab392df03cd"]
pub struct Scene { pub struct Scene {
pub entities: Vec<Entity>, pub world: World,
}
#[derive(Debug)]
pub struct Entity {
pub entity: u32,
pub components: Vec<DynamicProperties>,
} }
impl Scene { impl Scene {
pub fn from_world(world: &World, component_registry: &ComponentRegistry) -> Self { pub fn new(world: World) -> Self {
let mut scene = Scene::default(); Self { world }
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 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 anyhow::Result;
use bevy_asset::AssetLoader; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
use bevy_ecs::{FromResources, Resources}; use bevy_ecs::{FromResources, Resources};
use bevy_property::PropertyTypeRegistry; use bevy_property::PropertyTypeRegistry;
use bevy_type_registry::TypeRegistry; use bevy_type_registry::TypeRegistry;
use parking_lot::RwLock; use parking_lot::RwLock;
use serde::de::DeserializeSeed; use serde::de::DeserializeSeed;
use std::{path::Path, sync::Arc}; use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
pub struct SceneLoader { pub struct SceneLoader {
@ -22,15 +22,16 @@ impl FromResources for SceneLoader {
} }
} }
impl AssetLoader<Scene> for SceneLoader { impl AssetLoader for SceneLoader {
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<Scene> { fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> {
let registry = self.property_type_registry.read(); let registry = self.property_type_registry.read();
let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
let scene_deserializer = SceneDeserializer { let scene_deserializer = SceneDeserializer {
property_type_registry: &registry, property_type_registry: &registry,
}; };
let scene = scene_deserializer.deserialize(&mut deserializer)?; let scene = scene_deserializer.deserialize(&mut deserializer)?;
Ok(scene) load_context.set_default_asset(LoadedAsset::new(scene));
Ok(())
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

View file

@ -1,7 +1,7 @@
use crate::Scene; use crate::{DynamicScene, Scene};
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_ecs::{Resources, World}; use bevy_ecs::{EntityMap, Resources, World};
use bevy_type_registry::TypeRegistry; use bevy_type_registry::TypeRegistry;
use bevy_utils::HashMap; use bevy_utils::HashMap;
use thiserror::Error; use thiserror::Error;
@ -9,7 +9,7 @@ use uuid::Uuid;
#[derive(Debug)] #[derive(Debug)]
struct InstanceInfo { struct InstanceInfo {
entity_map: HashMap<u32, bevy_ecs::Entity>, entity_map: EntityMap,
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
@ -24,10 +24,12 @@ impl InstanceId {
#[derive(Default)] #[derive(Default)]
pub struct SceneSpawner { pub struct SceneSpawner {
spawned_scenes: HashMap<Handle<Scene>, Vec<InstanceId>>, spawned_scenes: HashMap<Handle<Scene>, Vec<InstanceId>>,
spawned_dynamic_scenes: HashMap<Handle<DynamicScene>, Vec<InstanceId>>,
spawned_instances: HashMap<InstanceId, InstanceInfo>, spawned_instances: HashMap<InstanceId, InstanceInfo>,
scene_asset_event_reader: EventReader<AssetEvent<Scene>>, scene_asset_event_reader: EventReader<AssetEvent<DynamicScene>>,
scenes_to_instance: Vec<Handle<Scene>>, dynamic_scenes_to_spawn: Vec<Handle<DynamicScene>>,
scenes_to_despawn: Vec<Handle<Scene>>, scenes_to_spawn: Vec<Handle<Scene>>,
scenes_to_despawn: Vec<Handle<DynamicScene>>,
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -35,33 +37,99 @@ pub enum SceneSpawnError {
#[error("Scene contains an unregistered component.")] #[error("Scene contains an unregistered component.")]
UnregisteredComponent { type_name: String }, UnregisteredComponent { type_name: String },
#[error("Scene does not exist. Perhaps it is still loading?")] #[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 { impl SceneSpawner {
pub fn spawn(&mut self, scene_handle: Handle<Scene>) { pub fn spawn_dynamic(&mut self, scene_handle: Handle<DynamicScene>) {
self.scenes_to_instance.push(scene_handle); 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); self.scenes_to_despawn.push(scene_handle);
} }
pub fn despawn_sync( pub fn despawn_sync(
&mut self, &mut self,
world: &mut World, world: &mut World,
scene_handle: Handle<Scene>, scene_handle: Handle<DynamicScene>,
) -> Result<(), SceneSpawnError> { ) -> 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 { for instance_id in instance_ids {
if let Some(instance) = self.spawned_instances.get(&instance_id) { if let Some(instance) = self.spawned_instances.get(&instance_id) {
for entity in instance.entity_map.values() { 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(()) Ok(())
} }
@ -74,9 +142,42 @@ impl SceneSpawner {
) -> Result<(), SceneSpawnError> { ) -> Result<(), SceneSpawnError> {
let instance_id = InstanceId::new(); let instance_id = InstanceId::new();
let mut instance_info = InstanceInfo { 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); self.spawned_instances.insert(instance_id, instance_info);
let spawned = self let spawned = self
.spawned_scenes .spawned_scenes
@ -86,56 +187,22 @@ impl SceneSpawner {
Ok(()) 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( pub fn update_spawned_scenes(
&mut self, &mut self,
world: &mut World, world: &mut World,
resources: &Resources, resources: &Resources,
scene_handles: &[Handle<Scene>], scene_handles: &[Handle<DynamicScene>],
) -> Result<(), SceneSpawnError> { ) -> Result<(), SceneSpawnError> {
for scene_handle in scene_handles { 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() { for instance_id in spawned_instances.iter() {
if let Some(instance_info) = self.spawned_instances.get_mut(instance_id) { 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, world: &mut World,
resources: &Resources, resources: &Resources,
) -> Result<(), SceneSpawnError> { ) -> 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 { for scene_handle in scenes_to_spawn {
match self.spawn_sync(world, resources, scene_handle) { match self.spawn_sync(world, resources, scene_handle) {
Ok(_) => {} Ok(_) => {}
Err(SceneSpawnError::NonExistentScene { .. }) => { Err(SceneSpawnError::NonExistentRealScene { handle }) => {
self.scenes_to_instance.push(scene_handle) self.scenes_to_spawn.push(handle)
} }
Err(err) => return Err(err), Err(err) => return Err(err),
} }
@ -175,7 +254,7 @@ impl SceneSpawner {
pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) { pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) {
let mut scene_spawner = resources.get_mut::<SceneSpawner>().unwrap(); 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(); let mut updated_spawned_scenes = Vec::new();
for event in scene_spawner for event in scene_spawner
@ -183,8 +262,8 @@ pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) {
.iter(&scene_asset_events) .iter(&scene_asset_events)
{ {
if let AssetEvent::Modified { handle } = event { if let AssetEvent::Modified { handle } = event {
if scene_spawner.spawned_scenes.contains_key(handle) { if scene_spawner.spawned_dynamic_scenes.contains_key(handle) {
updated_spawned_scenes.push(*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 anyhow::Result;
use bevy_property::{ use bevy_property::{
property_serde::{DynamicPropertiesDeserializer, DynamicPropertiesSerializer}, property_serde::{DynamicPropertiesDeserializer, DynamicPropertiesSerializer},
@ -11,12 +11,12 @@ use serde::{
}; };
pub struct SceneSerializer<'a> { pub struct SceneSerializer<'a> {
pub scene: &'a Scene, pub scene: &'a DynamicScene,
pub registry: &'a PropertyTypeRegistry, pub registry: &'a PropertyTypeRegistry,
} }
impl<'a> SceneSerializer<'a> { 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 } SceneSerializer { scene, registry }
} }
} }
@ -86,13 +86,13 @@ pub struct SceneDeserializer<'a> {
} }
impl<'a, 'de> DeserializeSeed<'de> for 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> fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let mut scene = Scene::default(); let mut scene = DynamicScene::default();
scene.entities = deserializer.deserialize_seq(SceneEntitySeqVisiter { scene.entities = deserializer.deserialize_seq(SceneEntitySeqVisiter {
property_type_registry: self.property_type_registry, property_type_registry: self.property_type_registry,
})?; })?;

View file

@ -1,7 +1,9 @@
use bevy_asset::{self, Handle}; use bevy_asset::{self, Handle};
use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture}; 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 struct ColorMaterial {
pub color: Color, pub color: Color,
#[shader_def] #[shader_def]

View file

@ -33,12 +33,13 @@ use bevy_render::{
render_graph::RenderGraph, render_graph::RenderGraph,
shader::asset_shader_defs_system, shader::asset_shader_defs_system,
}; };
use bevy_type_registry::TypeUuid;
use sprite::sprite_system; use sprite::sprite_system;
#[derive(Default)] #[derive(Default)]
pub struct SpritePlugin; 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 { impl Plugin for SpritePlugin {
fn build(&self, app: &mut AppBuilder) { fn build(&self, app: &mut AppBuilder) {
@ -50,18 +51,18 @@ impl Plugin for SpritePlugin {
asset_shader_defs_system::<ColorMaterial>.system(), asset_shader_defs_system::<ColorMaterial>.system(),
); );
let resources = app.resources(); let resources = app.resources_mut();
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap(); let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
render_graph.add_sprite_graph(resources); render_graph.add_sprite_graph(resources);
let mut meshes = resources.get_mut::<Assets<Mesh>>().unwrap(); 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, QUAD_HANDLE,
// Use a flipped quad because the camera is facing "forward" but quads should face backward // 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))), 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}, shader::{Shader, ShaderStage, ShaderStages},
texture::TextureFormat, texture::TextureFormat,
}; };
use bevy_type_registry::TypeUuid;
pub const SPRITE_PIPELINE_HANDLE: Handle<PipelineDescriptor> = 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> = 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 { pub fn build_sprite_sheet_pipeline(shaders: &mut Assets<Shader>) -> PipelineDescriptor {
PipelineDescriptor { PipelineDescriptor {
@ -150,8 +151,8 @@ impl SpriteRenderGraphBuilder for RenderGraph {
let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap(); let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap(); let mut shaders = resources.get_mut::<Assets<Shader>>().unwrap();
pipelines.set(SPRITE_PIPELINE_HANDLE, build_sprite_pipeline(&mut shaders)); pipelines.set_untracked(SPRITE_PIPELINE_HANDLE, build_sprite_pipeline(&mut shaders));
pipelines.set( pipelines.set_untracked(
SPRITE_SHEET_PIPELINE_HANDLE, SPRITE_SHEET_PIPELINE_HANDLE,
build_sprite_sheet_pipeline(&mut shaders), build_sprite_sheet_pipeline(&mut shaders),
); );

View file

@ -3,8 +3,10 @@ use bevy_asset::{Assets, Handle};
use bevy_ecs::{Query, Res}; use bevy_ecs::{Query, Res};
use bevy_math::Vec2; use bevy_math::Vec2;
use bevy_render::{renderer::RenderResources, texture::Texture}; 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 struct Sprite {
pub size: Vec2, pub size: Vec2,
#[render_resources(ignore)] #[render_resources(ignore)]
@ -43,9 +45,9 @@ pub fn sprite_system(
match sprite.resize_mode { match sprite.resize_mode {
SpriteResizeMode::Manual => continue, SpriteResizeMode::Manual => continue,
SpriteResizeMode::Automatic => { SpriteResizeMode::Automatic => {
let material = materials.get(&handle).unwrap(); let material = materials.get(handle).unwrap();
if let Some(texture_handle) = material.texture { if let Some(ref texture_handle) = material.texture {
if let Some(texture) = textures.get(&texture_handle) { if let Some(texture) = textures.get(texture_handle) {
sprite.size = texture.size; sprite.size = texture.size;
} }
} }

View file

@ -7,10 +7,12 @@ use bevy_render::{
renderer::{RenderResource, RenderResources}, renderer::{RenderResource, RenderResources},
texture::Texture, texture::Texture,
}; };
use bevy_type_registry::TypeUuid;
use bevy_utils::HashMap; use bevy_utils::HashMap;
/// An atlas containing multiple textures (like a spritesheet or a tilemap) /// 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 { pub struct TextureAtlas {
/// The handle to the texture in which the sprites are stored /// The handle to the texture in which the sprites are stored
pub texture: Handle<Texture>, pub texture: Handle<Texture>,
@ -138,9 +140,9 @@ impl TextureAtlas {
self.textures.is_empty() 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 self.texture_handles
.as_ref() .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.width() as f32,
packed_location.height() 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 }); texture_rects.push(Rect { min, max });
self.place_texture(&mut atlas_texture, texture, packed_location); 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_math = { path = "../bevy_math", version = "0.2.1" }
bevy_render = { path = "../bevy_render", version = "0.2.1" } bevy_render = { path = "../bevy_render", version = "0.2.1" }
bevy_sprite = { path = "../bevy_sprite", 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" } bevy_utils = { path = "../bevy_utils", version = "0.2.1" }
# other # 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> { fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> {
context.set_pipeline( context.set_pipeline(
draw, draw,
bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE, &bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE,
&PipelineSpecialization { &PipelineSpecialization {
sample_count: self.msaa.samples, sample_count: self.msaa.samples,
..Default::default() ..Default::default()
@ -56,13 +56,13 @@ impl<'a> Drawable for DrawableText<'a> {
let render_resource_context = &**context.render_resource_context; let render_resource_context = &**context.render_resource_context;
if let Some(RenderResourceId::Buffer(quad_vertex_buffer)) = 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); draw.set_vertex_buffer(0, quad_vertex_buffer, 0);
} }
let mut indices = 0..0; let mut indices = 0..0;
if let Some(RenderResourceId::Buffer(quad_index_buffer)) = render_resource_context 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); draw.set_index_buffer(quad_index_buffer, 0);
if let Some(buffer_info) = render_resource_context.get_buffer_info(quad_index_buffer) { 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 glyph_height = glyph_rect.height();
let atlas_render_resource_bindings = self let atlas_render_resource_bindings = self
.asset_render_resource_bindings .asset_render_resource_bindings
.get_mut(glyph_atlas_info.texture_atlas) .get_mut(&glyph_atlas_info.texture_atlas)
.unwrap(); .unwrap();
context.set_bind_groups_from_bindings( context.set_bind_groups_from_bindings(
draw, draw,

View file

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

View file

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

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