Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic concept for customizing Component reference type #7499

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
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 crates/bevy_ecs/macros/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
};

let storage = storage_path(&bevy_ecs_path, attrs.storage);
let refs = attrs.refs;

ast.generics
.make_where_clause()
Expand All @@ -44,15 +45,21 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
type Storage = #storage;
// TODO: better error than `this trait takes 0 generic arguments but 1 generic argument was supplied`
type Refs = #refs<Self>;
// type Refs = #bevy_ecs_path::component_refs::ChangeDetectionRefs<Self>;
// type Refs = #bevy_ecs_path::component_refs::UnwrappedRefs<Self>;
}
})
}

pub const COMPONENT: Symbol = Symbol("component");
pub const STORAGE: Symbol = Symbol("storage");
pub const REFS: Symbol = Symbol("refs");

struct Attrs {
storage: StorageTy,
refs: syn::Type,
}

#[derive(Clone, Copy)]
Expand All @@ -70,6 +77,11 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {

let mut attrs = Attrs {
storage: StorageTy::Table,
refs: syn::Type::Path(syn::TypePath {
path: crate::BevyManifest::default()
.get_path("bevy_ecs::component_refs::ChangeDetectionRefs"),
qself: None,
}),
};

for meta in meta_items {
Expand All @@ -92,6 +104,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
}
};
}
Meta(NameValue(m)) if m.path == REFS => {
attrs.refs = syn::parse_str(get_lit_str(STORAGE, &m.lit)?.value().as_str())?;
}
Meta(meta_item) => {
return Err(Error::new_spanned(
meta_item.path(),
Expand Down
24 changes: 23 additions & 1 deletion crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Types for declaring and storing [`Component`]s.

use crate::component_refs::ComponentRefs;
use crate::{
change_detection::MAX_CHANGE_AGE,
storage::{SparseSetIndex, Storages},
Expand Down Expand Up @@ -85,6 +86,26 @@ use std::{
/// [`Table`]: crate::storage::Table
/// [`SparseSet`]: crate::storage::SparseSet
///
/// # Choosing `Query` reference types
///
/// When used in [queries], components can return types other then the reference type specified. For example,
/// requesting `&mut Transform` in a query will result in a query that returns `Mut<Transform>`, a wrapper
/// that updates change detection information when it is mutably de-referenced. The reference types returned
/// can be overridden by specifying a [`ComponentRefs`] type as part of your `Component` derive.
///
/// For example, to disable change detection, add #[component(refs = UnwrappedRefs)]:
///
/// ```
/// # use bevy_ecs::component::Component;
/// #
/// #[derive(Component)]
/// #[component(storage = "SparseSet", refs = UnwrappedRefs)]
/// struct ComponentA;
/// ```
///
/// [`queries`]: crate::system::Query
/// [`ComponentRefs`] crate::component_refs::ComponentRefs
///
/// # Implementing the trait for foreign types
///
/// As a consequence of the [orphan rule], it is not possible to separate into two different crates the implementation of `Component` from the definition of a type.
Expand Down Expand Up @@ -142,8 +163,9 @@ use std::{
///
/// [`SyncCell`]: bevy_utils::synccell::SyncCell
/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html
pub trait Component: Send + Sync + 'static {
pub trait Component: Send + Sync + Sized + 'static {
type Storage: ComponentStorage;
type Refs: ComponentRefs<Self>;
}

pub struct TableStorage;
Expand Down
247 changes: 247 additions & 0 deletions crates/bevy_ecs/src/component_refs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
use crate::change_detection::{Mut, Ref, Ticks, TicksMut};
use crate::component::{Component, ComponentStorage, StorageType};
use crate::entity::Entity;
use crate::query::{ComponentFetch, DebugCheckedUnwrap};
use crate::storage::TableRow;
use bevy_ptr::UnsafeCellDeref;
use std::marker::PhantomData;

/// Trait for defining the types returned by &T and &mut T component queries.
pub trait ComponentRefs<T> {
type Ref<'w>: ComponentRef<'w, T>;
type MutRef<'w>: ComponentRef<'w, T>;

fn shrink_ref<'wlong: 'wshort, 'wshort>(item: Self::Ref<'wlong>) -> Self::Ref<'wshort>;
fn shrink_mut<'wlong: 'wshort, 'wshort>(item: Self::MutRef<'wlong>) -> Self::MutRef<'wshort>;
}

pub trait ComponentRef<'w, T> {
unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self;
}

/// Pass this to your component derives to override the default type returned by &T
/// and &mut T component queries. This can be useful if you don't need change detection for
/// a component and want to avoid the overhead.
///
/// Example:
/// ```rust
/// // TODO
/// ```
///
/// TODO expand on change detection features lost by using this
/// Use this with caution, because Changed queries will not work
///
/// Todo: Is it possible to disable verify for engine components like Transform? (probably not)
pub struct UnwrappedRefs<T: ?Sized> {
phantom: PhantomData<T>,
}
impl<T> ComponentRefs<T> for UnwrappedRefs<T>
where
T: Sized + Component,
{
type Ref<'w> = &'w T;
type MutRef<'w> = &'w mut T;

fn shrink_ref<'wlong: 'wshort, 'wshort>(item: Self::Ref<'wlong>) -> Self::Ref<'wshort> {
item
}

fn shrink_mut<'wlong: 'wshort, 'wshort>(item: Self::MutRef<'wlong>) -> Self::MutRef<'wshort> {
item
}
}

pub struct ChangeDetectionRefs<T: ?Sized> {
phantom: PhantomData<T>,
}
impl<T> ComponentRefs<T> for ChangeDetectionRefs<T>
where
T: Sized + Component,
{
type Ref<'w> = &'w T;
// type Ref<'w> = Ref<'w, T>;
type MutRef<'w> = Mut<'w, T>;

fn shrink_ref<'wlong: 'wshort, 'wshort>(item: Self::Ref<'wlong>) -> Self::Ref<'wshort> {
item
}

fn shrink_mut<'wlong: 'wshort, 'wshort>(item: Self::MutRef<'wlong>) -> Self::MutRef<'wshort> {
item
}
}

impl<'w, T> ComponentRef<'w, T> for &'w T
where
T: Component,
{
unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self {
match T::Storage::STORAGE_TYPE {
StorageType::Table => fetch
.table_data
.debug_checked_unwrap()
.0
.get(table_row.index())
.deref(),
StorageType::SparseSet => fetch
.sparse_set
.debug_checked_unwrap()
.get(entity)
.debug_checked_unwrap()
.deref(),
}
}
}
impl<'w, T> ComponentRef<'w, T> for Ref<'w, T>
where
T: Component,
{
unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self {
match T::Storage::STORAGE_TYPE {
StorageType::Table => {
let (table_components, added_ticks, changed_ticks) =
fetch.table_data.debug_checked_unwrap();
Ref {
value: table_components.get(table_row.index()).deref(),
ticks: Ticks {
added: added_ticks.get(table_row.index()).deref(),
changed: changed_ticks.get(table_row.index()).deref(),
change_tick: fetch.change_tick,
last_change_tick: fetch.last_change_tick,
},
}
}
StorageType::SparseSet => {
let (component, ticks) = fetch
.sparse_set
.debug_checked_unwrap()
.get_with_ticks(entity)
.debug_checked_unwrap();
Ref {
value: component.deref(),
ticks: Ticks::from_tick_cells(ticks, fetch.last_change_tick, fetch.change_tick),
}
}
}
}
}
impl<'w, T> ComponentRef<'w, T> for &'w mut T
where
T: Component,
{
unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self {
match T::Storage::STORAGE_TYPE {
StorageType::Table => fetch
.table_data
.debug_checked_unwrap()
.0
.get(table_row.index())
.deref_mut(),
StorageType::SparseSet => fetch
.sparse_set
.debug_checked_unwrap()
.get_with_ticks(entity)
.debug_checked_unwrap()
.0
.assert_unique()
.deref_mut(),
}
}
}

impl<'w, T> ComponentRef<'w, T> for Mut<'w, T>
where
T: Component,
{
unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self {
match T::Storage::STORAGE_TYPE {
StorageType::Table => {
let (table_components, added_ticks, changed_ticks) =
fetch.table_data.debug_checked_unwrap();
Mut {
value: table_components.get(table_row.index()).deref_mut(),
ticks: TicksMut {
added: added_ticks.get(table_row.index()).deref_mut(),
changed: changed_ticks.get(table_row.index()).deref_mut(),
change_tick: fetch.change_tick,
last_change_tick: fetch.last_change_tick,
},
}
}
StorageType::SparseSet => {
let (component, ticks) = fetch
.sparse_set
.debug_checked_unwrap()
.get_with_ticks(entity)
.debug_checked_unwrap();
Mut {
value: component.assert_unique().deref_mut(),
ticks: TicksMut::from_tick_cells(
ticks,
fetch.last_change_tick,
fetch.change_tick,
),
}
}
}
}
}

mod test {
use crate::{self as bevy_ecs, prelude::*};

#[test]
fn test_explicit_change_detection_derive() {
#[derive(Component)]
#[component(refs = "ChangeDetectionRefs")]
struct Number(usize);

let mut world = World::new();
world.spawn(Number(1));

fn detect_changed(query: Query<Entity, Changed<Number>>) -> usize {
return query.iter().count();
}
let mut detect_changed_system = IntoSystem::into_system(detect_changed);
detect_changed_system.initialize(&mut world);

fn update_number(mut query: Query<&mut Number>) {
query.single_mut().0 = 3;
}
let mut update_number_system = IntoSystem::into_system(update_number);
update_number_system.initialize(&mut world);

// initialize last_run tick
detect_changed_system.run((), &mut world);
update_number_system.run((), &mut world);
assert_eq!(detect_changed_system.run((), &mut world), 1);
}

#[test]
fn test_unwrapped_refs() {
#[derive(Component, Debug)]
#[component(refs = "UnwrappedRefs")]
struct Number(usize);

let mut world = World::new();
world.spawn(Number(1));

fn detect_changed(query: Query<Entity, Changed<Number>>) -> usize {
return query.iter().count();
}
let mut detect_changed_system = IntoSystem::into_system(detect_changed);
detect_changed_system.initialize(&mut world);

fn update_number(mut query: Query<&mut Number>) {
query.single_mut().0 = 3;
dbg!(query.single_mut());
}
let mut update_number_system = IntoSystem::into_system(update_number);
update_number_system.initialize(&mut world);

// initialize last_run tick
detect_changed_system.run((), &mut world);
update_number_system.run((), &mut world);
assert_eq!(detect_changed_system.run((), &mut world), 0);
}
}
2 changes: 2 additions & 0 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod archetype;
pub mod bundle;
pub mod change_detection;
pub mod component;
pub mod component_refs;
pub mod entity;
pub mod event;
pub mod query;
Expand All @@ -31,6 +32,7 @@ pub mod prelude {
bundle::Bundle,
change_detection::{DetectChanges, DetectChangesMut, Mut, Ref},
component::Component,
component_refs::{ChangeDetectionRefs, UnwrappedRefs},
entity::Entity,
event::{EventReader, EventWriter, Events},
query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without},
Expand Down
Loading