diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 440fe31468df7..1f56d840fd45b 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -2,7 +2,9 @@ use bevy_macro_utils::{get_lit_str, Symbol}; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; -use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result}; +use syn::{ + parse_macro_input, parse_quote, DataStruct, DeriveInput, Error, Fields, Ident, Path, Result, +}; pub fn derive_component(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); @@ -23,18 +25,61 @@ pub fn derive_component(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let (lens_type, ext) = match attrs.lens { + Lens::None => (quote! {#bevy_ecs_path::lens::NoopLens}, None), + Lens::Declared(decl) => (decl, None), + Lens::Derived => { + let inner_type = match &ast.data { + syn::Data::Struct(DataStruct { + fields: Fields::Unnamed(f), + .. + }) if f.unnamed.len() == 1 => &f.unnamed[0].ty, + _ => return quote! { + compile_error!("Automatic lensing is only available for unit structs with one element") + }.into(), + }; + ( + quote! {Self}, + Some(quote! { + impl #impl_generics #bevy_ecs_path::lens::Lens for #struct_name #type_generics #where_clause { + type In = Self; + type Out = #inner_type; + + fn get(input: &Self::In) -> &Self::Out { + &input.0 + } + fn get_mut(input: &mut Self::In) -> &mut Self::Out { + &mut input.0 + } + } + }), + ) + } + }; + TokenStream::from(quote! { + #ext + impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { type Storage = #storage; + type DefaultLens = #lens_type; } }) } pub const COMPONENT: Symbol = Symbol("component"); pub const STORAGE: Symbol = Symbol("storage"); +pub const LENS: Symbol = Symbol("lens"); struct Attrs { storage: StorageTy, + lens: Lens, +} + +enum Lens { + None, + Declared(TokenStream2), + Derived, } #[derive(Clone, Copy)] @@ -52,11 +97,12 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { let mut attrs = Attrs { storage: StorageTy::Table, + lens: Lens::None, }; for meta in meta_items { use syn::{ - Meta::NameValue, + Meta::{NameValue, Path}, NestedMeta::{Lit, Meta}, }; match meta { @@ -75,6 +121,16 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } }; } + Meta(NameValue(m)) if m.path == LENS => { + attrs.lens = Lens::Declared(get_lit_str(LENS, &m.lit)?.value().as_str().parse()?); + } + Meta(Path(p)) + if p.leading_colon.is_none() + && p.segments.len() == 1 + && p.segments[0].ident == LENS => + { + attrs.lens = Lens::Derived; + } Meta(meta_item) => { return Err(Error::new_spanned( meta_item.path(), diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 369d9ecfab486..f00b63ecef315 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -1,6 +1,6 @@ //! Types that detect when their internal data mutate. -use crate::{component::ComponentTicks, system::Resource}; +use crate::{component::ComponentTicks, lens::Lens, system::Resource}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; use std::ops::{Deref, DerefMut}; @@ -116,6 +116,32 @@ macro_rules! impl_into_inner { }; } +macro_rules! impl_lens { + ($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => { + impl<$($generics),* $(: $traits)?> $name<$($generics),*> { + #[inline] + pub fn lens>(self) -> Mut<'a, L::Out> { + Mut { + value: L::get_mut(self.value), + ticks: self.ticks, + } + } + + #[inline] + pub fn lens_borrow<'b, L: Lens>(&'b mut self) -> Mut<'b, L::Out> { + Mut { + value: L::get_mut(self.value), + ticks: Ticks { + component_ticks: self.ticks.component_ticks, + last_change_tick: self.ticks.last_change_tick, + change_tick: self.ticks.change_tick, + }, + } + } + } + }; +} + macro_rules! impl_debug { ($name:ident < $( $generics:tt ),+ >, $($traits:ident)?) => { impl<$($generics),* $(: $traits)?> std::fmt::Debug for $name<$($generics),*> @@ -154,6 +180,7 @@ pub struct ResMut<'a, T: Resource> { } change_detection_impl!(ResMut<'a, T>, T, Resource); +impl_lens!(ResMut<'a, T>, T, Resource); impl_into_inner!(ResMut<'a, T>, T, Resource); impl_debug!(ResMut<'a, T>, Resource); @@ -175,6 +202,7 @@ pub struct NonSendMut<'a, T: 'static> { } change_detection_impl!(NonSendMut<'a, T>, T,); +impl_lens!(NonSendMut<'a, T>, T,); impl_into_inner!(NonSendMut<'a, T>, T,); impl_debug!(NonSendMut<'a, T>,); @@ -185,6 +213,7 @@ pub struct Mut<'a, T> { } change_detection_impl!(Mut<'a, T>, T,); +impl_lens!(Mut<'a, T>, T,); impl_into_inner!(Mut<'a, T>, T,); impl_debug!(Mut<'a, T>,); @@ -199,3 +228,70 @@ pub struct ReflectMut<'a> { change_detection_impl!(ReflectMut<'a>, dyn Reflect,); #[cfg(feature = "bevy_reflect")] impl_into_inner!(ReflectMut<'a>, dyn Reflect,); + +mod ops_passthrough { + use std::ops::*; + + use super::Mut; + + macro_rules! binary_ops { + ($trait:ident, $method:ident) => { + impl + Copy> $trait for Mut<'_, T> { + type Output = T::Output; + + fn $method(self, rhs: Rhs) -> Self::Output { + T::$method(*self, rhs) + } + } + }; + } + macro_rules! unary_ops { + ($trait:ident, $method:ident) => { + impl $trait for Mut<'_, T> { + type Output = T::Output; + + fn $method(self) -> Self::Output { + T::$method(*self) + } + } + }; + } + macro_rules! assign_ops { + ($trait:ident, $method:ident) => { + impl> $trait for Mut<'_, T> { + fn $method(&mut self, rhs: Rhs) { + T::$method(&mut *self, rhs) + } + } + }; + } + + binary_ops!(Add, add); + binary_ops!(Sub, sub); + binary_ops!(Mul, mul); + binary_ops!(Div, div); + + binary_ops!(Rem, rem); + + binary_ops!(BitAnd, bitand); + binary_ops!(BitOr, bitor); + binary_ops!(BitXor, bitxor); + binary_ops!(Shr, shr); + binary_ops!(Shl, shl); + + unary_ops!(Neg, neg); + unary_ops!(Not, not); + + assign_ops!(AddAssign, add_assign); + assign_ops!(SubAssign, sub_assign); + assign_ops!(MulAssign, mul_assign); + assign_ops!(DivAssign, div_assign); + + assign_ops!(RemAssign, rem_assign); + + assign_ops!(BitAndAssign, bitand_assign); + assign_ops!(BitOrAssign, bitor_assign); + assign_ops!(BitXorAssign, bitxor_assign); + assign_ops!(ShrAssign, shr_assign); + assign_ops!(ShlAssign, shl_assign); +} diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index ebb9896a606d3..9ad2b16a3e634 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,6 +1,7 @@ //! Types for declaring and storing [`Component`]s. use crate::{ + lens::{Lens, NoopLens}, storage::{SparseSetIndex, Storages}, system::Resource, }; @@ -32,6 +33,8 @@ use std::{ /// Components can be grouped together into a [`Bundle`](crate::bundle::Bundle). pub trait Component: Send + Sync + 'static { type Storage: ComponentStorage; + + type DefaultLens: Lens; } pub struct TableStorage; @@ -61,6 +64,7 @@ where Self: Send + Sync + 'static, { type Storage = TableStorage; + type DefaultLens = NoopLens; } /// The storage used for a specific component type. diff --git a/crates/bevy_ecs/src/lens.rs b/crates/bevy_ecs/src/lens.rs new file mode 100644 index 0000000000000..9de0a868b25e7 --- /dev/null +++ b/crates/bevy_ecs/src/lens.rs @@ -0,0 +1,76 @@ +use std::marker::PhantomData; + +pub trait Lens { + type In: 'static; + type Out: 'static; + + fn get(input: &Self::In) -> &Self::Out; + fn get_mut(input: &mut Self::In) -> &mut Self::Out; +} + +pub struct ComposedLens(PhantomData<(A, B)>); + +impl Lens for ComposedLens +where + A: Lens, + B: Lens, +{ + type In = A::In; + type Out = B::Out; + + fn get(input: &Self::In) -> &Self::Out { + B::get(A::get(input)) + } + + fn get_mut(input: &mut Self::In) -> &mut Self::Out { + B::get_mut(A::get_mut(input)) + } +} + +pub struct NoopLens(PhantomData); + +impl Lens for NoopLens { + type In = T; + type Out = T; + + fn get(input: &Self::In) -> &Self::Out { + input + } + + fn get_mut(input: &mut Self::In) -> &mut Self::Out { + input + } +} + +#[macro_export] +macro_rules! declare_lens { + ($name:ident, $in:ty, $out:ty, $($path:tt).*) => { + struct $name; + + impl $crate::lens::Lens for $name { + type In = $in; + type Out = $out; + + fn get(input: &Self::In) -> &Self::Out { + &input$(.$path)* + } + + fn get_mut(input: &mut Self::In) -> &mut Self::Out { + &mut input$(.$path)* + } + } + }; +} + +#[macro_export] +macro_rules! composed_lens { + ($last:ty) => { + $last + }; + ($first:ty, $second:ty) => { + $crate::lens::ComposedLens<$first, $second> + }; + ($first:ty, $second:ty, $($rest:ty),*) => { + $crate::lens::ComposedLens + }; +} diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index ad93f91d0d150..88114556faf92 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -6,6 +6,7 @@ pub mod change_detection; pub mod component; pub mod entity; pub mod event; +pub mod lens; pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; @@ -26,7 +27,9 @@ pub mod prelude { component::Component, entity::Entity, event::{EventReader, EventWriter}, - query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without}, + query::{ + Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, Raw, RawMut, With, Without, + }, schedule::{ AmbiguitySetLabel, ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion, RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel, RunCriteriaPiping, diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index d68c64375e2e5..7d20bf4afa1f7 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -3,6 +3,7 @@ use crate::{ change_detection::Ticks, component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, entity::Entity, + lens::{Lens, NoopLens}, query::{Access, FilteredAccess}, storage::{ComponentSparseSet, Table, Tables}, world::{Mut, World}, @@ -495,10 +496,19 @@ impl<'w, 's> Fetch<'w, 's> for EntityFetch { } } +/// Equivalent to querying with `&T`, but ignores the default lens. Useful with generic code +pub struct Raw(PhantomData); + +impl WorldQuery for Raw { + type Fetch = ReadFetch>; + type State = ReadState; + type ReadOnlyFetch = ReadFetch>; +} + impl WorldQuery for &T { - type Fetch = ReadFetch; + type Fetch = ReadFetch; type State = ReadState; - type ReadOnlyFetch = ReadFetch; + type ReadOnlyFetch = ReadFetch; } /// The [`FetchState`] of `&T`. @@ -550,30 +560,31 @@ unsafe impl FetchState for ReadState { } /// The [`Fetch`] of `&T`. -#[doc(hidden)] -pub struct ReadFetch { +pub struct ReadFetch { table_components: NonNull, entity_table_rows: *const usize, entities: *const Entity, sparse_set: *const ComponentSparseSet, + lens: PhantomData, } -impl Clone for ReadFetch { +impl Clone for ReadFetch { fn clone(&self) -> Self { Self { table_components: self.table_components, entity_table_rows: self.entity_table_rows, entities: self.entities, sparse_set: self.sparse_set, + lens: PhantomData, } } } /// SAFETY: access is read only -unsafe impl ReadOnlyFetch for ReadFetch {} +unsafe impl ReadOnlyFetch for ReadFetch {} -impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { - type Item = &'w T; +impl<'w, 's, T: Component, L: Lens> Fetch<'w, 's> for ReadFetch { + type Item = &'w L::Out; type State = ReadState; const IS_DENSE: bool = { @@ -594,6 +605,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { entities: ptr::null::(), entity_table_rows: ptr::null::(), sparse_set: ptr::null::(), + lens: PhantomData, }; if T::Storage::STORAGE_TYPE == StorageType::SparseSet { value.sparse_set = world @@ -635,7 +647,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { #[inline] unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - match T::Storage::STORAGE_TYPE { + L::get(match T::Storage::STORAGE_TYPE { StorageType::Table => { let table_row = *self.entity_table_rows.add(archetype_index); &*self.table_components.as_ptr().add(table_row) @@ -644,24 +656,32 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { let entity = *self.entities.add(archetype_index); &*(*self.sparse_set).get(entity).unwrap().cast::() } - } + }) } #[inline] unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { - &*self.table_components.as_ptr().add(table_row) + L::get(&*self.table_components.as_ptr().add(table_row)) } } +/// Equivalent to querying with `&mut T`, but ignores the default lens. Useful with generic code +pub struct RawMut(PhantomData); + +impl WorldQuery for RawMut { + type Fetch = WriteFetch>; + type State = WriteState; + type ReadOnlyFetch = ReadOnlyWriteFetch>; +} + impl WorldQuery for &mut T { - type Fetch = WriteFetch; + type Fetch = WriteFetch; type State = WriteState; - type ReadOnlyFetch = ReadOnlyWriteFetch; + type ReadOnlyFetch = ReadOnlyWriteFetch; } /// The [`Fetch`] of `&mut T`. -#[doc(hidden)] -pub struct WriteFetch { +pub struct WriteFetch { table_components: NonNull, table_ticks: *const UnsafeCell, entities: *const Entity, @@ -669,9 +689,10 @@ pub struct WriteFetch { sparse_set: *const ComponentSparseSet, last_change_tick: u32, change_tick: u32, + lens: PhantomData, } -impl Clone for WriteFetch { +impl Clone for WriteFetch { fn clone(&self) -> Self { Self { table_components: self.table_components, @@ -681,29 +702,31 @@ impl Clone for WriteFetch { sparse_set: self.sparse_set, last_change_tick: self.last_change_tick, change_tick: self.change_tick, + lens: PhantomData, } } } /// The [`ReadOnlyFetch`] of `&mut T`. -#[doc(hidden)] -pub struct ReadOnlyWriteFetch { +pub struct ReadOnlyWriteFetch { table_components: NonNull, entities: *const Entity, entity_table_rows: *const usize, sparse_set: *const ComponentSparseSet, + lens: PhantomData, } /// SAFETY: access is read only -unsafe impl ReadOnlyFetch for ReadOnlyWriteFetch {} +unsafe impl ReadOnlyFetch for ReadOnlyWriteFetch {} -impl Clone for ReadOnlyWriteFetch { +impl Clone for ReadOnlyWriteFetch { fn clone(&self) -> Self { Self { table_components: self.table_components, entities: self.entities, entity_table_rows: self.entity_table_rows, sparse_set: self.sparse_set, + lens: PhantomData, } } } @@ -756,8 +779,8 @@ unsafe impl FetchState for WriteState { } } -impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { - type Item = Mut<'w, T>; +impl<'w, 's, T: Component, L: Lens> Fetch<'w, 's> for WriteFetch { + type Item = Mut<'w, L::Out>; type State = WriteState; const IS_DENSE: bool = { @@ -781,6 +804,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { table_ticks: ptr::null::>(), last_change_tick, change_tick, + lens: PhantomData, }; if T::Storage::STORAGE_TYPE == StorageType::SparseSet { value.sparse_set = world @@ -825,7 +849,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { StorageType::Table => { let table_row = *self.entity_table_rows.add(archetype_index); Mut { - value: &mut *self.table_components.as_ptr().add(table_row), + value: L::get_mut(&mut *self.table_components.as_ptr().add(table_row)), ticks: Ticks { component_ticks: &mut *(*self.table_ticks.add(table_row)).get(), change_tick: self.change_tick, @@ -838,7 +862,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { let (component, component_ticks) = (*self.sparse_set).get_with_ticks(entity).unwrap(); Mut { - value: &mut *component.cast::(), + value: L::get_mut(&mut *component.cast::()), ticks: Ticks { component_ticks: &mut *component_ticks, change_tick: self.change_tick, @@ -852,7 +876,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { #[inline] unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { Mut { - value: &mut *self.table_components.as_ptr().add(table_row), + value: L::get_mut(&mut *self.table_components.as_ptr().add(table_row)), ticks: Ticks { component_ticks: &mut *(*self.table_ticks.add(table_row)).get(), change_tick: self.change_tick, @@ -862,8 +886,8 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { } } -impl<'w, 's, T: Component> Fetch<'w, 's> for ReadOnlyWriteFetch { - type Item = &'w T; +impl<'w, 's, T: Component, L: Lens> Fetch<'w, 's> for ReadOnlyWriteFetch { + type Item = &'w L::Out; type State = WriteState; const IS_DENSE: bool = { @@ -884,6 +908,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadOnlyWriteFetch { entities: ptr::null::(), entity_table_rows: ptr::null::(), sparse_set: ptr::null::(), + lens: PhantomData, }; if T::Storage::STORAGE_TYPE == StorageType::SparseSet { value.sparse_set = world @@ -922,7 +947,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadOnlyWriteFetch { #[inline] unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - match T::Storage::STORAGE_TYPE { + L::get(match T::Storage::STORAGE_TYPE { StorageType::Table => { let table_row = *self.entity_table_rows.add(archetype_index); &*self.table_components.as_ptr().add(table_row) @@ -931,12 +956,12 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadOnlyWriteFetch { let entity = *self.entities.add(archetype_index); &*(*self.sparse_set).get(entity).unwrap().cast::() } - } + }) } #[inline] unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { - &*self.table_components.as_ptr().add(table_row) + L::get(&*self.table_components.as_ptr().add(table_row)) } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 528926ebd4be9..8fd722846b051 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -15,7 +15,7 @@ use bevy_ecs::{ component::Component, entity::Entity, event::EventReader, - prelude::With, + prelude::{RawMut, With}, query::Added, reflect::ReflectComponent, system::{Commands, ParamSet, Query, Res, ResMut}, @@ -155,7 +155,7 @@ pub fn camera_system( windows: Res, images: Res>, mut queries: ParamSet<( - Query<(Entity, &mut Camera, &mut T)>, + Query<(Entity, &mut Camera, RawMut)>, Query>, )>, ) { diff --git a/crates/bevy_render/src/render_component.rs b/crates/bevy_render/src/render_component.rs index f4e901587ee8a..acaf6d79cde7d 100644 --- a/crates/bevy_render/src/render_component.rs +++ b/crates/bevy_render/src/render_component.rs @@ -103,7 +103,7 @@ fn prepare_uniform_components( render_device: Res, render_queue: Res, mut component_uniforms: ResMut>, - components: Query<(Entity, &C)>, + components: Query<(Entity, Raw)>, ) where C: AsStd140 + Clone, { diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index e7cd28c78ce61..ed2cc4d026d03 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -124,7 +124,7 @@ pub fn calculate_bounds( } pub fn update_frusta( - mut views: Query<(&GlobalTransform, &T, &mut Frustum)>, + mut views: Query<(&GlobalTransform, Raw, &mut Frustum)>, ) { for (transform, projection, mut frustum) in views.iter_mut() { let view_projection = diff --git a/examples/2d/sprite_sheet.rs b/examples/2d/sprite_sheet.rs index d3d313fab226e..97c854600de35 100644 --- a/examples/2d/sprite_sheet.rs +++ b/examples/2d/sprite_sheet.rs @@ -8,7 +8,8 @@ fn main() { .run(); } -#[derive(Component, Deref, DerefMut)] +#[derive(Component)] +#[component(lens)] struct AnimationTimer(Timer); fn animate_sprite( diff --git a/examples/ecs/component_change_detection.rs b/examples/ecs/component_change_detection.rs index 01473c7749bc0..c60f6477ff55c 100644 --- a/examples/ecs/component_change_detection.rs +++ b/examples/ecs/component_change_detection.rs @@ -13,6 +13,7 @@ fn main() { } #[derive(Component, Debug)] +#[component(lens)] struct MyComponent(f64); fn setup(mut commands: Commands) { @@ -24,7 +25,7 @@ fn change_component(time: Res