Skip to content
Open
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
50 changes: 37 additions & 13 deletions crates/bevy_ecs/macros/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use bevy_macro_utils::BevyManifest;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Data, DeriveInput,
Fields, FieldsUnnamed, Index, Path, Result, Token, WhereClause,
parse::ParseStream, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned,
Data, DeriveInput, Fields, FieldsUnnamed, Ident, Index, Path, Result, Token, WhereClause,
};

const TEMPLATE_DEFAULT_ATTRIBUTE: &str = "default";
const TEMPLATE_ATTRIBUTE: &str = "template";
const BUILT_IN_ATTRIBUTE: &str = "built_in";

pub(crate) fn derive_from_template(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
Expand Down Expand Up @@ -332,6 +333,12 @@ struct StructImpl {
template_field_clones: Vec<proc_macro2::TokenStream>,
}

enum TemplateType {
FromTemplate,
BuiltIn,
Manual(Path),
}

fn struct_impl(fields: &Fields, bevy_ecs: &Path, is_enum: bool) -> Result<StructImpl> {
let mut template_fields = Vec::with_capacity(fields.len());
let mut template_field_builds = Vec::with_capacity(fields.len());
Expand All @@ -344,22 +351,39 @@ fn struct_impl(fields: &Fields, bevy_ecs: &Path, is_enum: bool) -> Result<Struct
let ident = &field.ident;
let ty = &field.ty;
let index = Index::from(index);
let mut template_type = None;
let mut template_type = TemplateType::FromTemplate;
for attr in &field.attrs {
if attr.path().is_ident(TEMPLATE_ATTRIBUTE) {
let Ok(path) = attr.parse_args::<Path>() else {
return Err(syn::Error::new(
attr.span(),
"Expected a Template type path",
));
};
template_type = Some(quote!(#path));
attr.parse_args_with(|stream: ParseStream| {
let forked = stream.fork();
let ident = forked.parse::<Ident>()?;
if ident == BUILT_IN_ATTRIBUTE {
stream.parse::<Ident>()?;
template_type = TemplateType::BuiltIn;
} else {
if let Ok(path) = stream.parse::<Path>() {
template_type = TemplateType::Manual(path);
} else {
return Err(syn::Error::new(
attr.span(),
"Expected a Template type path",
));
}
}
Ok(())
})?;
}
}

if template_type.is_none() {
template_type = Some(quote!(<#ty as #bevy_ecs::template::FromTemplate>::Template));
}
let template_type = match template_type {
TemplateType::FromTemplate => {
quote!(<#ty as #bevy_ecs::template::FromTemplate>::Template)
}
TemplateType::BuiltIn => {
quote!(<#ty as #bevy_ecs::template::BuiltInTemplate>::Template)
}
TemplateType::Manual(path) => quote! {#path},
};

if is_named {
template_fields.push(quote! {
Expand Down
174 changes: 165 additions & 9 deletions crates/bevy_ecs/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,72 @@ impl ScopedEntities {
/// }
/// }
/// ```
///
/// [`FromTemplate`] is automatically implemented for anything that is [`Default`] and [`Clone`]. "Built in" collection types like
/// [`Option`] and [`Vec`] pick up this "blanket" implementation, which is generally a good thing because it means these collection
/// types work with [`FromTemplate`] derives by default. However if the items in the collection have a custom [`FromTemplate`] impl
/// (ex: a manual implementation like `Handle<T>` for assets or an explicit [`FromTemplate`] derive), then relying on a [`Default`] /
/// [`Clone`] implementation doesn't work, as that won't run the template logic!
///
/// Therefore, cases like [`Option<Handle<T>>`] need something other than [`FromTemplate`] to determine the type. One option is to specify
/// the template manually:
///
/// ```
/// # use bevy_ecs::{prelude::*, template::{TemplateContext, OptionTemplate}};
/// # use core::marker::PhantomData;
/// # struct Handle<T>(PhantomData<T>);
/// # struct HandleTemplate<T>(PhantomData<T>);
/// # struct Image;
/// # impl<T> FromTemplate for Handle<T> {
/// # type Template = HandleTemplate<T>;
/// # }
/// # impl<T> Template for HandleTemplate<T> {
/// # type Output = Handle<T>;
/// # fn build_template(&self, context: &mut TemplateContext) -> Result<Self::Output> {
/// # unimplemented!()
/// # }
/// # fn clone_template(&self) -> Self {
/// # unimplemented!()
/// # }
/// # }
/// #[derive(FromTemplate)]
/// struct Widget {
/// #[template(OptionTemplate<HandleTemplate<Image>>)]
/// image: Option<Handle<Image>>
/// }
/// ```
///
/// However that is a bit of a mouthfull! This is where [`BuiltInTemplate`] comes in. It fills the same role
/// as [`FromTemplate`], but has no blanket implementation for [`Default`] and [`Clone`], meaning we can have
/// custom implementations for types like [`Option`] and [`Vec`].
///
/// If you deriving [`FromTemplate`] and you have a "built in" type like [`Option<Handle<T>>`] which has custom template logic,
/// annotate it with the `template(built_in)` attribute to use [`BuiltInTemplate`] instead of [`FromTemplate`]:
///
/// ```
/// # use bevy_ecs::{prelude::*, template::TemplateContext};
/// # use core::marker::PhantomData;
/// # struct Handle<T>(PhantomData<T>);
/// # struct HandleTemplate<T>(PhantomData<T>);
/// # struct Image;
/// # impl<T> FromTemplate for Handle<T> {
/// # type Template = HandleTemplate<T>;
/// # }
/// # impl<T> Template for HandleTemplate<T> {
/// # type Output = Handle<T>;
/// # fn build_template(&self, context: &mut TemplateContext) -> Result<Self::Output> {
/// # unimplemented!()
/// # }
/// # fn clone_template(&self) -> Self {
/// # unimplemented!()
/// # }
/// # }
/// #[derive(FromTemplate)]
/// struct Widget {
/// #[template(built_in)]
/// image: Option<Handle<Image>>
/// }
/// ```
pub trait FromTemplate: Sized {
/// The [`Template`] for this type.
type Template: Template;
Expand Down Expand Up @@ -431,26 +497,116 @@ pub fn template<F: Fn(&mut TemplateContext) -> Result<O>, O>(func: F) -> FnTempl
FnTemplate(func)
}

/// A [`Template`] for Option.
pub struct OptionTemplate<T>(Option<T>);
/// Roughly equivalent to [`FromTemplate`], but does not have a blanket implementation for [`Default`] + [`Clone`] types.
/// This is generally used for common generic collection types like [`Option`] and [`Vec`], which have [`Default`] + [`Clone`] impls and
/// therefore also pick up the [`FromTemplate`] behavior. This is fine when the `T` in [`Option<T>`] is not "templated"
/// (ex: does not have an explicit [`FromTemplate`] derive). But if `T` is "templated", such as [`Option<Handle<T>>`], then it would require
/// a manual `#[template(OptionTemplate<HandleTemplate<T>>)]` field annotation. This isn't fun to type out.
///
/// [`BuiltInTemplate`] enables equivalent "template type inference", by annotating a field with a type that implements [`BuiltInTemplate`] with
/// `#[template(built_in)]`.
pub trait BuiltInTemplate: Sized {
/// The template to consider the "built in" template for this type.
type Template: Template;
}

impl<T> Default for OptionTemplate<T> {
fn default() -> Self {
Self(None)
impl<T: FromTemplate> BuiltInTemplate for Option<T> {
type Template = OptionTemplate<T::Template>;
}

impl<T: FromTemplate> BuiltInTemplate for Vec<T> {
type Template = VecTemplate<T::Template>;
}

/// A [`Template`] for [`Option`].
#[derive(Default)]
pub enum OptionTemplate<T> {
/// Template of [`Option::Some`].
Some(T),
/// Template of [`Option::None`].
#[default]
None,
}

impl<T> From<Option<T>> for OptionTemplate<T> {
fn from(value: Option<T>) -> Self {
match value {
Some(value) => OptionTemplate::Some(value),
None => OptionTemplate::None,
}
}
}

impl<T> From<T> for OptionTemplate<T> {
fn from(value: T) -> Self {
OptionTemplate::Some(value)
}
}

impl<T: Template> Template for OptionTemplate<T> {
type Output = Option<T::Output>;

fn build_template(&self, context: &mut TemplateContext) -> Result<Self::Output> {
Ok(match &self.0 {
Some(template) => Some(template.build_template(context)?),
None => None,
Ok(match &self {
OptionTemplate::Some(template) => Some(template.build_template(context)?),
OptionTemplate::None => None,
})
}

fn clone_template(&self) -> Self {
OptionTemplate(self.0.as_ref().map(Template::clone_template))
match self {
OptionTemplate::Some(value) => OptionTemplate::Some(value.clone_template()),
OptionTemplate::None => OptionTemplate::None,
}
}
}

/// A [`Template`] for [`Vec`].
pub struct VecTemplate<T>(pub Vec<T>);

impl<T> Default for VecTemplate<T> {
fn default() -> Self {
Self(Vec::new())
}
}

impl<T: Template> Template for VecTemplate<T> {
type Output = Vec<T::Output>;

fn build_template(&self, context: &mut TemplateContext) -> Result<Self::Output> {
let mut output = Vec::with_capacity(self.0.len());
for value in &self.0 {
output.push(value.build_template(context)?);
}
Ok(output)
}

fn clone_template(&self) -> Self {
VecTemplate(self.0.iter().map(Template::clone_template).collect())
}
}

#[cfg(test)]
mod tests {
use crate::prelude::*;
use alloc::string::{String, ToString};

#[test]
fn option_template() {
#[derive(FromTemplate)]
struct Handle(String);

#[derive(FromTemplate)]
struct Foo {
#[template(built_in)]
handle: Option<Handle>,
}

let mut world = World::new();
let foo_template = FooTemplate {
handle: Some(HandleTemplate("handle_path".to_string())).into(),
};
let foo = world.spawn_empty().build_template(&foo_template).unwrap();
assert_eq!(foo.handle.unwrap().0, "handle_path".to_string());
}
}
17 changes: 17 additions & 0 deletions crates/bevy_ecs/src/world/entity_access/world_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::{
relationship::RelationshipHookMode,
resource::Resource,
storage::{SparseSets, Table},
template::{EntityScopes, ScopedEntities, Template, TemplateContext},
world::{
error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, ComponentEntry,
DynamicComponentFetch, EntityMut, EntityRef, FilteredEntityMut, FilteredEntityRef, Mut,
Expand Down Expand Up @@ -1594,6 +1595,22 @@ impl<'w> EntityWorldMut<'w> {
self.entity
}

/// Creates a new [`TemplateContext`] for this entity and passes it into the given `func`.
pub fn template_context<T>(
&mut self,
func: impl FnOnce(&mut TemplateContext) -> crate::error::Result<T>,
) -> crate::error::Result<T> {
let mut scoped_entities = ScopedEntities::new(0);
let entity_scopes = EntityScopes::default();
let mut context = TemplateContext::new(self, &mut scoped_entities, &entity_scopes);
func(&mut context)
}

/// Builds the given template using a [`TemplateContext`] generated for this entity.
pub fn build_template<T: Template>(&mut self, template: &T) -> crate::error::Result<T::Output> {
self.template_context(|context| template.build_template(context))
}

/// This despawns this entity if it is currently spawned, storing the new [`EntityGeneration`](crate::entity::EntityGeneration) in [`Self::entity`] but not freeing it.
pub(crate) fn despawn_no_free_with_caller(&mut self, caller: MaybeLocation) {
// setup
Expand Down
10 changes: 3 additions & 7 deletions crates/bevy_sprite/src/sprite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ use bevy_asset::{AsAssetId, AssetId, Assets, Handle};
use bevy_camera::visibility::{self, Visibility, VisibilityClass};
use bevy_color::Color;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::Component,
reflect::ReflectComponent,
template::{FromTemplate, OptionTemplate},
};
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout, TextureAtlasTemplate};
use bevy_ecs::{component::Component, reflect::ReflectComponent, template::FromTemplate};
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout};
use bevy_math::{Rect, UVec2, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, PartialReflect, Reflect};
use bevy_transform::components::Transform;
Expand All @@ -24,7 +20,7 @@ pub struct Sprite {
/// The image used to render the sprite
pub image: Handle<Image>,
/// The (optional) texture atlas used to render the sprite
#[template(OptionTemplate<TextureAtlasTemplate>)]
#[template(built_in)]
pub texture_atlas: Option<TextureAtlas>,
/// The sprite's color tint
pub color: Color,
Expand Down
10 changes: 3 additions & 7 deletions crates/bevy_sprite/src/sprite_mesh.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
use bevy_asset::{Assets, Handle};
use bevy_camera::visibility::{Visibility, VisibilityClass};
use bevy_color::Color;
use bevy_ecs::{
component::Component,
reflect::ReflectComponent,
template::{FromTemplate, OptionTemplate},
};
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout, TextureAtlasTemplate};
use bevy_ecs::{component::Component, reflect::ReflectComponent, template::FromTemplate};
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout};
use bevy_math::{Rect, UVec2, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, PartialReflect, Reflect};
use bevy_transform::components::Transform;
Expand All @@ -24,7 +20,7 @@ pub struct SpriteMesh {
/// The image used to render the sprite
pub image: Handle<Image>,
/// The (optional) texture atlas used to render the sprite
#[template(OptionTemplate<TextureAtlasTemplate>)]
#[template(built_in)]
pub texture_atlas: Option<TextureAtlas>,
/// The sprite's color tint
pub color: Color,
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ui/src/widget/image.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{ComputedUiRenderTargetInfo, ContentSize, Measure, MeasureArgs, Node, NodeMeasure};
use bevy_asset::{AsAssetId, AssetId, Assets, Handle};
use bevy_color::Color;
use bevy_ecs::{prelude::*, template::OptionTemplate};
use bevy_image::{prelude::*, TextureAtlasTemplate, TRANSPARENT_IMAGE_HANDLE};
use bevy_ecs::prelude::*;
use bevy_image::{prelude::*, TRANSPARENT_IMAGE_HANDLE};
use bevy_math::{Rect, UVec2, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_sprite::TextureSlicer;
Expand All @@ -23,7 +23,7 @@ pub struct ImageNode {
/// This defaults to a [`TRANSPARENT_IMAGE_HANDLE`], which points to a fully transparent 1x1 texture.
pub image: Handle<Image>,
/// The (optional) texture atlas used to render the image.
#[template(OptionTemplate<TextureAtlasTemplate>)]
#[template(built_in)]
pub texture_atlas: Option<TextureAtlas>,
/// Whether the image should be flipped along its x-axis.
pub flip_x: bool,
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_window/src/cursor/custom_cursor.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::cursor::CursorIcon;
use alloc::string::String;
use bevy_asset::Handle;
use bevy_ecs::template::{FromTemplate, OptionTemplate};
use bevy_image::{Image, TextureAtlas, TextureAtlasTemplate};
use bevy_ecs::template::FromTemplate;
use bevy_image::{Image, TextureAtlas};
use bevy_math::URect;

#[cfg(feature = "bevy_reflect")]
Expand All @@ -20,7 +20,7 @@ pub struct CustomCursorImage {
/// or 32 bit float rgba. PNG images work well for this.
pub handle: Handle<Image>,
/// An optional texture atlas used to render the image.
#[template(OptionTemplate<TextureAtlasTemplate>)]
#[template(built_in)]
pub texture_atlas: Option<TextureAtlas>,
/// Whether the image should be flipped along its x-axis.
///
Expand Down