Skip to content
Merged
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
18 changes: 11 additions & 7 deletions crates/bevy_ecs/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub use bevy_ecs_macros::FromTemplate;

use crate::{
entity::Entity,
error::Result,
error::{BevyError, Result},
resource::Resource,
world::{EntityWorldMut, Mut, World},
};
Expand Down Expand Up @@ -402,11 +402,15 @@ impl<T: Clone + Default + Unpin> FromTemplate for T {
pub trait SpecializeFromTemplate: Sized {}

/// A [`Template`] reference to an [`Entity`].
#[derive(Default)]
pub enum EntityTemplate {
/// A reference to a specific [`Entity`]
Entity(Entity),
/// A reference to an entity via a [`ScopedEntityIndex`]
ScopedEntityIndex(ScopedEntityIndex),
/// An entity has not been specified. Building a template with this variant will result in an error.
#[default]
None,
}

/// An entity index within the current [`TemplateContext`], which is defined by a scope
Expand All @@ -423,12 +427,6 @@ pub struct ScopedEntityIndex {
pub index: usize,
}

impl Default for EntityTemplate {
fn default() -> Self {
Self::ScopedEntityIndex(ScopedEntityIndex { scope: 0, index: 0 })
}
}

impl From<Entity> for EntityTemplate {
fn from(entity: Entity) -> Self {
Self::Entity(entity)
Expand All @@ -444,6 +442,11 @@ impl Template for EntityTemplate {
Self::ScopedEntityIndex(scoped_entity_index) => {
context.get_scoped_entity(*scoped_entity_index)
}
Self::None => {
return Err(BevyError::error(
"Failed to specify an entity for this EntityTemplate",
))
}
})
}

Expand All @@ -453,6 +456,7 @@ impl Template for EntityTemplate {
Self::ScopedEntityIndex(scoped_entity_index) => {
Self::ScopedEntityIndex(*scoped_entity_index)
}
Self::None => Self::None,
}
}
}
Expand Down
183 changes: 108 additions & 75 deletions crates/bevy_scene/macros/src/bsn/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,34 @@ impl<const ALLOW_FLAT: bool> Bsn<ALLOW_FLAT> {
/// Accumulates errors in [`BsnCodegenCtx`].
pub fn try_to_tokens(&self, ctx: &mut BsnCodegenCtx) -> syn::Result<TokenStream> {
let bevy_scene = ctx.bevy_scene;
let entries: Vec<_> = self
.entries
.iter()
.map(|entry| {
entry
.try_to_tokens(ctx)
.unwrap_or_else(|e| e.to_compile_error())
})
.collect();
Ok(quote! { #bevy_scene::auto_nest_tuple!(#(#entries),*) })
let mut combined_patches = Vec::new();
let mut scene_impls = Vec::new();
for entry in &self.entries {
match entry.try_to_tokens(ctx) {
Ok(EntryResult::CombinedSceneFunction(patch)) => combined_patches.push(patch),
Ok(EntryResult::NewSceneImpl(scene_impl)) => {
if !combined_patches.is_empty() {
let patches = combined_patches.drain(..);
scene_impls.push(quote! {
#bevy_scene::SceneFunction(move |_context, _scene| {
#(#patches)*
})
});
}
scene_impls.push(scene_impl)
}
Err(err) => scene_impls.push(err.to_compile_error()),
}
}
if !combined_patches.is_empty() {
let patches = combined_patches.drain(..);
scene_impls.push(quote! {
#bevy_scene::SceneFunction(move |_context, _scene| {
#(#patches)*
})
});
}
Ok(quote! { #bevy_scene::auto_nest_tuple!(#(#scene_impls),*) })
}

pub fn to_tokens(&self, ctx: &mut BsnCodegenCtx) -> TokenStream {
Expand All @@ -147,34 +165,16 @@ impl<const ALLOW_FLAT: bool> Bsn<ALLOW_FLAT> {
}
}

fn from_template_patch(
ctx: &mut BsnCodegenCtx,
ty: &BsnType,
is_scene_component: bool,
) -> syn::Result<TokenStream> {
let mut assigns = Vec::new();
let target = PatchTarget {
path: &[Member::Named(Ident::new(
"value",
proc_macro2::Span::call_site(),
))],
is_ref: true,
};
ty.to_patch_tokens(ctx, &mut assigns, true, false, is_scene_component, target)?;
let path = &ty.path;
let bevy_scene = ctx.bevy_scene;
Ok(quote! {
<#path as #bevy_scene::PatchFromTemplate>::patch(move |value, _context| {
#(#assigns)*
})
})
enum EntryResult {
CombinedSceneFunction(TokenStream),
NewSceneImpl(TokenStream),
}

impl BsnEntry {
fn try_to_tokens(&self, ctx: &mut BsnCodegenCtx) -> syn::Result<TokenStream> {
fn try_to_tokens(&self, ctx: &mut BsnCodegenCtx) -> syn::Result<EntryResult> {
let (bevy_scene, bevy_ecs) = (ctx.bevy_scene, ctx.bevy_ecs);

match self {
Ok(match self {
BsnEntry::TemplatePatch(ty) => {
let mut assigns = Vec::new();
let target = PatchTarget {
Expand All @@ -186,52 +186,74 @@ impl BsnEntry {
};
ty.to_patch_tokens(ctx, &mut assigns, true, false, true, target)?;
let path = &ty.path;
let bevy_scene = ctx.bevy_scene;
Ok(quote! {
<#path as #bevy_scene::PatchTemplate>::patch_template(move |value, _context| {
EntryResult::CombinedSceneFunction(if assigns.is_empty() {
quote! {
let _ = _scene.get_or_insert_template::<#path>(_context);
}
} else {
quote! {
let value = _scene.get_or_insert_template::<#path>(_context);
#(#assigns)*
}
})
}
BsnEntry::FromTemplatePatch(ty) => {
let mut assigns = Vec::new();
let target = PatchTarget {
path: &[Member::Named(Ident::new(
"value",
proc_macro2::Span::call_site(),
))],
is_ref: true,
};
ty.to_patch_tokens(ctx, &mut assigns, true, false, false, target)?;
let path = &ty.path;
EntryResult::CombinedSceneFunction(if assigns.is_empty() {
quote! {
let _ = _scene.get_or_insert_template::<<#path as #bevy_ecs::template::FromTemplate>::Template>(_context);
}
} else {
quote! {
let value = _scene.get_or_insert_template::<<#path as #bevy_ecs::template::FromTemplate>::Template>(_context);
#(#assigns)*
})
}
})
}
BsnEntry::FromTemplatePatch(ty) => from_template_patch(ctx, ty, false),
BsnEntry::TemplateConst {
type_path,
const_ident,
} => Ok(quote! {
<#type_path as #bevy_scene::PatchTemplate>::patch_template(move |value, _context| {
*value = #type_path::#const_ident;
})
} => EntryResult::CombinedSceneFunction(quote! {
let value = _scene.get_or_insert_template::<#type_path>(_context);
*value = #type_path::#const_ident;
}),
BsnEntry::SceneExpression(block) => Ok(quote! {{#block}}),
BsnEntry::SceneExpression(block) => EntryResult::NewSceneImpl(quote! {{#block}}),
BsnEntry::TemplateConstructor(BsnConstructor {
type_path,
function,
args,
}) => Ok(quote! {
<#type_path as #bevy_scene::PatchTemplate>::patch_template(move |value, _context| {
*value = #type_path::#function(#args);
})
}) => EntryResult::CombinedSceneFunction(quote! {
let value = _scene.get_or_insert_template::<#type_path>(_context);
*value = #type_path::#function(#args);
}),
BsnEntry::FromTemplateConstructor(BsnConstructor {
type_path,
function,
args,
}) => Ok(quote! {
<#type_path as #bevy_scene::PatchFromTemplate>::patch(move |value, _context| {
*value = <#type_path as #bevy_ecs::template::FromTemplate>::Template::#function(#args);
})
}) => EntryResult::CombinedSceneFunction(quote! {
let value = _scene.get_or_insert_template::<<#type_path as #bevy_ecs::template::FromTemplate>::Template>(_context);
*value = <#type_path as #bevy_ecs::template::FromTemplate>::Template::#function(#args);
}),
BsnEntry::RelatedSceneList(BsnRelatedSceneList {
scene_list,
relationship_path,
}) => {
let scenes = scene_list.0.to_tokens(ctx);
Ok(quote! {
EntryResult::NewSceneImpl(quote! {
#bevy_scene::RelatedScenes::<<#relationship_path as #bevy_ecs::relationship::RelationshipTarget>
::Relationship, _>::new(#scenes)
})
}
BsnEntry::InheritedScene(s) => Ok(match s {
BsnEntry::InheritedScene(s) => EntryResult::NewSceneImpl(match s {
BsnInheritedScene::Asset(lit) => quote! {
#bevy_scene::InheritSceneAsset::from(#lit)
},
Expand All @@ -245,24 +267,32 @@ impl BsnEntry {
let mut assignments = Vec::new();
let props = format_ident!("props");
let props_ref = format_ident!("props_ref");
bsn_type.to_patch_tokens(
ctx,
&mut assignments,
false,
true,
true,
PatchTarget {
path: &[Member::Named(props_ref.clone())],
is_ref: true,
},
)?;
let type_path = &bsn_type.path;
let from_template_patch = from_template_patch(ctx, bsn_type, true)?;
let target = PatchTarget {
path: &[Member::Named(props_ref.clone())],
is_ref: true,
};
bsn_type.to_patch_tokens(ctx, &mut assignments, false, true, true, target)?;
let mut assigns = Vec::new();
let target = PatchTarget {
path: &[Member::Named(Ident::new(
"value",
proc_macro2::Span::call_site(),
))],
is_ref: true,
};
bsn_type.to_patch_tokens(ctx, &mut assigns, true, false, true, target)?;
let path = &bsn_type.path;
let bevy_scene = ctx.bevy_scene;
let from_template_patch = quote! {
<#path as #bevy_scene::PatchFromTemplate>::patch(move |value, _context| {
#(#assigns)*
})
};
quote! {{
let mut #props = <<#type_path as #bevy_scene::SceneComponent>::Props as #FQDefault>::default();
let mut #props = <<#path as #bevy_scene::SceneComponent>::Props as #FQDefault>::default();
let #props_ref = &mut #props;
#(#assignments)*
(<#type_path as #bevy_scene::SceneComponent>::scene(#props), #from_template_patch)
(<#path as #bevy_scene::SceneComponent>::scene(#props), #from_template_patch)
}}
}
BsnInheritedScene::Expression(tokens) => quote! {
Expand All @@ -271,16 +301,15 @@ impl BsnEntry {
}),
BsnEntry::Name(ident) => {
let (name, index) = (ident.to_string(), ctx.entity_refs.get(ident.to_string()));
Ok(quote! {
#bevy_scene::NameEntityReference { name: #bevy_ecs::name::Name(#name.into()), index: #index }
EntryResult::CombinedSceneFunction(quote! {
#bevy_scene::NameEntityReference { name: #bevy_ecs::name::Name(#name.into()), index: #index }.resolve_inline(_context, _scene);
})
}
BsnEntry::NameExpression(expr) => Ok(quote! {
<#bevy_ecs::name::Name as #bevy_scene::PatchFromTemplate>::patch(move |value, _context| {
*value = #bevy_ecs::name::Name({#expr}.into());
})
BsnEntry::NameExpression(expr) => EntryResult::CombinedSceneFunction(quote! {
let value = _scene.get_or_insert_template::<<#bevy_ecs::name::Name as #bevy_ecs::template::FromTemplate>::Template>(_context);
*value = #bevy_ecs::name::Name({#expr}.into());
}),
}
})
}
}

Expand Down Expand Up @@ -449,6 +478,10 @@ impl BsnType {
}
}
BsnFields::Tuple(fields) => {
// Tuple fields can't be props
if is_props {
return Ok(());
}
for (i, field) in fields.iter().enumerate() {
if let Err(err) = self.process_field(
ctx,
Expand Down
27 changes: 27 additions & 0 deletions crates/bevy_scene/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1873,6 +1873,33 @@ mod tests {
assert!(entity.contains::<Widget>());
}

#[test]
fn scene_component_name_reference() {
#[derive(SceneComponent, FromTemplate)]
struct Widget(pub Entity);

impl Widget {
fn scene() -> impl Scene {
bsn! {}
}
}

let scene = bsn! {
#Name
Children [
:Widget(#Name)
]
};

let mut app = test_app();
let world = app.world_mut();
let entity = world.spawn_scene(scene).unwrap().id();
let root = world.entity(entity);
let children = root.get::<Children>().unwrap();
let child_widget = world.entity(children[0]).get::<Widget>().unwrap();
assert_eq!(child_widget.0, entity);
}

#[test]
fn drop_is_called_for_uninserted_components() {
#[derive(Component, FromTemplate)]
Expand Down
Loading
Loading