Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3032,6 +3032,21 @@ description = "Demonstrates how to use BSN to compose scenes"
category = "Scene"
wasm = true

[[example]]
name = "bsn_asset_catalog"
path = "examples/scene/bsn_asset_catalog.rs"
doc-scrape-examples = true

[package.metadata.example.bsn_asset_catalog]
name = "BSN Asset Catalog"
description = "Demonstrates loading named material definitions from a BSN asset catalog"
category = "Scene"
wasm = true

[[example]]
name = "dynamic_bsn"
path = "examples/scene/dynamic_bsn.rs"

# Shaders
[[package.metadata.example_category]]
name = "Shaders"
Expand Down
31 changes: 31 additions & 0 deletions assets/scenes/example.bsn
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Example BSN file.

#Root
bevy_transform::components::transform::Transform
bevy_camera::visibility::Visibility::Visible
bevy_ecs::hierarchy::Children [
bevy_camera::components::Camera3d
bevy_transform::components::transform::Transform {
translation: glam::Vec3 { x: 0.7, y: 0.7, z: 1.0 },
rotation: glam::Quat { x: -0.15037778, y: 0.2968788, z: 0.04740241, w: 0.941808 },
}
bevy_camera::visibility::Visibility::Visible
bevy_light::probe::EnvironmentMapLight {
diffuse_map: "environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2",
specular_map: "environment_maps/pisa_specular_rgb9e5_zstd.ktx2",
intensity: 250.0,
},

bevy_transform::components::transform::Transform
// Embed an asset as a handle:
bevy_scene::components::SceneRoot("models/FlightHelmet/FlightHelmet.gltf#Scene0"),

bevy_light::directional_light::DirectionalLight {
shadow_maps_enabled: true,
}
// A template:
@bevy_light::cascade::CascadeShadowConfigBuilder {
num_cascades: 1,
maximum_distance: 1.6,
}
]
34 changes: 34 additions & 0 deletions assets/scenes/material_catalog.bsn
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Example BSN asset catalog: named materials for reuse across scenes.
//
// Each named entry creates an asset that can be loaded via:
// asset_server.load("scenes/material_catalog.bsn#PolishedMetal")

bevy_ecs::hierarchy::Children [
#PolishedMetal
bevy_pbr::pbr_material::StandardMaterial {
metallic: 1.0,
perceptual_roughness: 0.05,
reflectance: 1.0,
},

#BrushedMetal
bevy_pbr::pbr_material::StandardMaterial {
metallic: 1.0,
perceptual_roughness: 0.4,
reflectance: 0.8,
},

#RoughStone
bevy_pbr::pbr_material::StandardMaterial {
metallic: 0.0,
perceptual_roughness: 0.9,
reflectance: 0.3,
},

#Plastic
bevy_pbr::pbr_material::StandardMaterial {
metallic: 0.0,
perceptual_roughness: 0.3,
reflectance: 0.5,
},
]
8 changes: 5 additions & 3 deletions crates/bevy_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use crate::{
ErasedAssetIndex, ReflectHandle, UntypedAssetId,
};
use alloc::sync::Arc;
use bevy_ecs::template::{FromTemplate, SpecializeFromTemplate, Template, TemplateContext};
use bevy_ecs::{reflect::{ReflectFromTemplate, ReflectTemplate}, template::{FromTemplate, SpecializeFromTemplate, Template, TemplateContext}};
use bevy_platform::collections::Equivalent;
use bevy_reflect::{Reflect, TypePath};
use bevy_reflect::{FromReflect, Reflect, TypePath, prelude::ReflectDefault};
use core::{
any::TypeId,
hash::{Hash, Hasher},
Expand Down Expand Up @@ -130,7 +130,7 @@ impl core::fmt::Debug for StrongHandle {
///
/// [`Handle::Strong`], via [`StrongHandle`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
#[derive(Reflect)]
#[reflect(Debug, Hash, PartialEq, Clone, Handle)]
#[reflect(Debug, Hash, PartialEq, Clone, Handle, FromTemplate)]
pub enum Handle<A: Asset> {
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
Expand Down Expand Up @@ -208,6 +208,8 @@ impl<T: Asset> FromTemplate for Handle<T> {
type Template = HandleTemplate<T>;
}

#[derive(Reflect)]
#[reflect(Default, Template)]
pub enum HandleTemplate<T: Asset> {
Path(AssetPath<'static>),
Handle(Handle<T>),
Expand Down
35 changes: 35 additions & 0 deletions crates/bevy_asset/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,41 @@ impl<'a> LoadContext<'a> {
handle
}

/// Add a type-erased [`ErasedLoadedAsset`] as a labeled sub-asset. Used for
/// registering assets created via reflection where the concrete type is not
/// known at compile time.
///
/// See [`AssetPath`] for more on labeled assets.
pub fn add_loaded_labeled_asset_erased(
&mut self,
label: impl Into<CowArc<'static, str>>,
loaded_asset: ErasedLoadedAsset,
asset_type_id: TypeId,
) -> UntypedHandle {
let label = label.into();
let labeled_path = self.asset_path.clone().with_label(label.clone());
let handle = self
.asset_server
.get_or_create_path_handle_erased(labeled_path, asset_type_id, None);
let asset = LabeledAsset {
asset: loaded_asset,
handle: handle.clone(),
};
match self.label_to_asset_index.entry(label) {
Entry::Occupied(entry) => {
let index = *entry.get();
self.labeled_assets[index] = asset;
}
Entry::Vacant(entry) => {
entry.insert(self.labeled_assets.len());
self.asset_id_to_asset_index
.insert(handle.id(), self.labeled_assets.len());
self.labeled_assets.push(asset);
}
}
handle
}

/// Returns `true` if an asset with the label `label` exists in this context.
///
/// See [`AssetPath`] for more on labeled assets.
Expand Down
15 changes: 15 additions & 0 deletions crates/bevy_asset/src/reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use bevy_reflect::{
};

use crate::{
loader::{ErasedLoadedAsset, LoadedAsset},
Asset, AssetId, AssetPath, AssetServer, Assets, Handle, InvalidGenerationError, LoadContext,
UntypedAssetId, UntypedHandle,
};
Expand Down Expand Up @@ -39,6 +40,7 @@ pub struct ReflectAsset {
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = UntypedAssetId> + 'w>,
remove: fn(&mut World, UntypedAssetId) -> Option<Box<dyn Reflect>>,
into_loaded_asset: fn(&dyn PartialReflect) -> Option<ErasedLoadedAsset>,
}

impl ReflectAsset {
Expand Down Expand Up @@ -154,6 +156,15 @@ impl ReflectAsset {
pub fn ids<'w>(&self, world: &'w World) -> impl Iterator<Item = UntypedAssetId> + 'w {
(self.ids)(world)
}

/// Convert a reflected asset value into an [`ErasedLoadedAsset`] suitable
/// for registering as a labeled sub-asset via [`LoadContext`].
pub fn into_loaded_asset(
&self,
value: &dyn PartialReflect,
) -> Option<ErasedLoadedAsset> {
(self.into_loaded_asset)(value)
}
}

impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
Expand Down Expand Up @@ -199,6 +210,10 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
let value = assets.remove(asset_id.typed_debug_checked());
value.map(|value| Box::new(value) as Box<dyn Reflect>)
},
into_loaded_asset: |value| {
let asset: A = FromReflect::from_reflect(value)?;
Some(LoadedAsset::from(asset).into())
},
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ std = [
"arrayvec/std",
"log/std",
"bevy_platform/std",
"downcast-rs/std",
]

## `critical-section` provides the building blocks for synchronization primitives
Expand Down Expand Up @@ -127,7 +126,6 @@ log = { version = "0.4", default-features = false }
bumpalo = "3"
subsecond = { version = "0.7.0-rc.0", optional = true }
slotmap = { version = "1.0.7", default-features = false }
downcast-rs = { version = "2", default-features = false }

concurrent-queue = { version = "2.5.0", default-features = false }
[target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies]
Expand Down
26 changes: 26 additions & 0 deletions crates/bevy_ecs/macros/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use syn::{

const TEMPLATE_DEFAULT_ATTRIBUTE: &str = "default";
const TEMPLATE_ATTRIBUTE: &str = "template";
const TEMPLATE_REFLECT: &str = "reflect";

pub(crate) fn derive_from_template(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
Expand All @@ -21,6 +22,27 @@ pub(crate) fn derive_from_template(input: TokenStream) -> TokenStream {
let is_pub = matches!(ast.vis, syn::Visibility::Public(_));
let maybe_pub = if is_pub { quote!(pub) } else { quote!() };

let should_make_template_reflectable = ast
.attrs
.iter()
.filter(|attr| attr.path().is_ident(&TEMPLATE_ATTRIBUTE))
.any(|template_attr| {
let mut found = false;
let _ = template_attr.parse_nested_meta(|meta| {
found = found || meta.path.is_ident(TEMPLATE_REFLECT);
Ok(())
});
found
});
let maybe_reflect = if should_make_template_reflectable {
quote! {
#[derive(Reflect)]
#[reflect(Default, Template)]
}
} else {
quote! {}
};

let template = match &ast.data {
Data::Struct(data_struct) => {
let result = match struct_impl(&data_struct.fields, &bevy_ecs, false) {
Expand All @@ -38,6 +60,7 @@ pub(crate) fn derive_from_template(input: TokenStream) -> TokenStream {
Fields::Named(_) => {
quote! {
#[allow(missing_docs)]
#maybe_reflect
#maybe_pub struct #template_ident #impl_generics #where_clause {
#(#template_fields,)*
}
Expand Down Expand Up @@ -69,6 +92,7 @@ pub(crate) fn derive_from_template(input: TokenStream) -> TokenStream {
Fields::Unnamed(_) => {
quote! {
#[allow(missing_docs)]
#maybe_reflect
#maybe_pub struct #template_ident #impl_generics (
#(#template_fields,)*
) #where_clause;
Expand Down Expand Up @@ -100,6 +124,7 @@ pub(crate) fn derive_from_template(input: TokenStream) -> TokenStream {
Fields::Unit => {
quote! {
#[allow(missing_docs)]
#maybe_reflect
#maybe_pub struct #template_ident;

impl #impl_generics #bevy_ecs::template::Template for #template_ident #type_generics #where_clause {
Expand Down Expand Up @@ -269,6 +294,7 @@ pub(crate) fn derive_from_template(input: TokenStream) -> TokenStream {

quote! {
#[allow(missing_docs)]
#maybe_reflect
#maybe_pub enum #template_ident #type_generics #where_clause {
#(#variant_definitions,)*
}
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_ecs/src/reflect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod from_world;
mod map_entities;
mod message;
mod resource;
mod template;

use bevy_utils::prelude::DebugName;
pub use bundle::{ReflectBundle, ReflectBundleFns};
Expand All @@ -29,6 +30,7 @@ pub use from_world::{ReflectFromWorld, ReflectFromWorldFns};
pub use map_entities::ReflectMapEntities;
pub use message::{ReflectMessage, ReflectMessageFns};
pub use resource::ReflectResource;
pub use template::{ReflectFromTemplate, ReflectTemplate};

/// A [`Resource`] storing [`TypeRegistry`] for
/// type registrations relevant to a whole app.
Expand Down
60 changes: 60 additions & 0 deletions crates/bevy_ecs/src/reflect/template.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Definitions for `FromTemplate` and `Template` reflection.

use alloc::boxed::Box;
use core::any::TypeId;

use bevy_reflect::{FromType, Reflect};
use derive_more::{Deref, DerefMut};

use crate::{
error::BevyError,
prelude::{FromTemplate, Template},
template::TemplateContext,
};

#[derive(Clone, Deref, DerefMut)]
pub struct ReflectFromTemplate(pub ReflectFromTemplateData);

#[derive(Clone, Deref, DerefMut)]
pub struct ReflectTemplate(pub ReflectTemplateData);

#[derive(Clone)]
pub struct ReflectFromTemplateData {
pub template_type_id: TypeId,
}

#[derive(Clone)]
pub struct ReflectTemplateData {
pub build_template:
fn(&dyn Reflect, &mut TemplateContext) -> Result<Box<dyn Reflect>, BevyError>,
}

impl<T> FromType<T> for ReflectFromTemplate
where
T: FromTemplate,
T::Template: 'static,
<T::Template as Template>::Output: Reflect,
{
fn from_type() -> Self {
ReflectFromTemplate(ReflectFromTemplateData {
template_type_id: TypeId::of::<T::Template>(),
})
}
}

impl<T> FromType<T> for ReflectTemplate
where
T: Template + 'static,
<T as Template>::Output: Reflect,
{
fn from_type() -> Self {
ReflectTemplate(ReflectTemplateData {
build_template: |this, context| {
let Some(this) = this.downcast_ref::<T>() else {
return Err("Unexpected `build_template` receiver type".into());
};
Ok(Box::new(<T as Template>::build_template(this, context)?))
},
})
}
}
Loading