Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions crates/bevy_core_pipeline/src/oit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl Default for OrderIndependentTransparencySettings {
// we can hook on_add to issue a warning in case `layer_count` is seemingly too high.
impl Component for OrderIndependentTransparencySettings {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
const UNSTABLE_TYPE_ID: u128 = 28539417283523;
type Mutability = Mutable;

fn on_add() -> Option<ComponentHook> {
Expand Down
15 changes: 15 additions & 0 deletions crates/bevy_ecs/examples/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ fn main() {
// We can label our systems to force a specific run-order between some of them
schedule.add_systems((
spawn_entities.in_set(SimulationSet::Spawn),
test,
test2.before(test),
print_counter_when_changed.after(SimulationSet::Spawn),
age_all_entities.in_set(SimulationSet::Age),
remove_old_entities.after(SimulationSet::Age),
Expand Down Expand Up @@ -90,6 +92,19 @@ fn print_changed_entities(
}
}

#[derive(Component)]
pub struct Awa;

fn test(query1: Query<&mut Age, With<Awa>>, query2: Query<&mut Age, Without<Awa>>) {

}


fn test2(query1: Query<&mut Age, With<Awa>>, query2: Query<&mut Age, Without<Awa>>) {

}


// This system iterates over all entities and increases their age in every frame
fn age_all_entities(mut entities: Query<&mut Age>) {
for mut age in &mut entities {
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ecs/macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.0-dev" }
syn = { version = "2.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
rand = "0.9.0"
[lints]
workspace = true

Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_ecs/macros/src/component.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use proc_macro::{TokenStream, TokenTree};
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote, ToTokens};
use rand::Rng;
use std::collections::HashSet;
use syn::{
parenthesized,
Expand Down Expand Up @@ -214,12 +215,14 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
(&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
)
};

let mut rng = rand::rng();
let unstable_type_id: u128 = rng.random();
// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
const UNSTABLE_TYPE_ID: u128 = #unstable_type_id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to make this value deterministic? I think that's preferable from it randomly changing whenever cargo clean && cargo build is ran.

Copy link
Contributor

@MarcGuiselin MarcGuiselin Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Using something like type_id::<Self>() would be ideal, but you'd need a nightly feature flag to make it const.

I guess you could do some sort of hashing that takes for input the component's ast, but that still probably isn't enough to avoid collisions with other components. It would also drastically increase the complexity of this pr.

I agree it'd be preferable, but what benefits are there to making it deterministic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah using type_id:: would be ideal, but we don't want to use any sort of deterministic count because I've heard from the grape vine that rustc might mess with things so we could end up with UB. Unless I have a guarantee that IDs are never going to repeat I would rather safely stick with a random u128.

type Mutability = #mutable_type;
fn register_required_components(
requiree: #bevy_ecs_path::component::ComponentId,
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ pub trait Component: Send + Sync + 'static {
/// A constant indicating the storage type used for this component.
const STORAGE_TYPE: StorageType;

/// A u128 randomly generated that is consistent per component type or 0 if it isn't derived automatically and can be used to differentiate them in const contexts.
const UNSTABLE_TYPE_ID: u128;
/// A marker type to assist Bevy with determining if this component is
/// mutable, or immutable. Mutable components will have [`Component<Mutability = Mutable>`],
/// while immutable components will instead have [`Component<Mutability = Immutable>`].
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ecs/src/observer/entity_observer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct ObservedBy(pub(crate) Vec<Entity>);

impl Component for ObservedBy {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
const UNSTABLE_TYPE_ID: u128 = 1;
type Mutability = Mutable;

fn on_remove() -> Option<ComponentHook> {
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_ecs/src/observer/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl ObserverState {

impl Component for ObserverState {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
const UNSTABLE_TYPE_ID: u128 = 2;
type Mutability = Mutable;

fn on_add() -> Option<ComponentHook> {
Expand Down Expand Up @@ -335,6 +336,7 @@ impl Observer {

impl Component for Observer {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
const UNSTABLE_TYPE_ID: u128 = 3;
type Mutability = Mutable;
fn on_add() -> Option<ComponentHook> {
Some(|world, context| {
Expand Down
61 changes: 58 additions & 3 deletions crates/bevy_ecs/src/query/fetch.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use crate::system::const_param_checking::{
AccessTreeContainer, ComponentAccess, ComponentAccessTree,
};
use crate::{
archetype::{Archetype, Archetypes},
bundle::Bundle,
Expand All @@ -11,6 +14,7 @@ use crate::{
FilteredEntityMut, FilteredEntityRef, Mut, Ref, World,
},
};
use bevy_ecs::system::const_param_checking::{WithFilterTree, WithoutFilterTree};
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use core::{cell::UnsafeCell, marker::PhantomData, panic::Location};
use smallvec::SmallVec;
Expand Down Expand Up @@ -284,6 +288,12 @@ pub unsafe trait QueryData: WorldQuery {
/// and is visible to the end user when calling e.g. `Query<Self>::get`.
type Item<'a>;

const COMPONENT_ACCESS_TREE_QUERY_DATA: ComponentAccessTree = ComponentAccessTree {
this: ComponentAccess::Ignore,
left: None,
right: None,
};

/// This function manually implements subtyping for the query items.
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort>;

Expand Down Expand Up @@ -1203,10 +1213,15 @@ unsafe impl<T: Component> WorldQuery for &T {
}

/// SAFETY: `Self` is the same as `Self::ReadOnly`
unsafe impl<T: Component> QueryData for &T {
unsafe impl<T: Component> QueryData for &T
where
for<'a> &'a T: crate::system::const_param_checking::AccessTreeContainer,
{
type ReadOnly = Self;
type Item<'w> = &'w T;

const COMPONENT_ACCESS_TREE_QUERY_DATA: ComponentAccessTree = Self::COMPONENT_ACCESS_TREE;

fn shrink<'wlong: 'wshort, 'wshort>(item: &'wlong T) -> &'wshort T {
item
}
Expand Down Expand Up @@ -1568,10 +1583,15 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
}

/// SAFETY: access of `&T` is a subset of `&mut T`
unsafe impl<'__w, T: Component<Mutability = Mutable>> QueryData for &'__w mut T {
unsafe impl<'__w, T: Component<Mutability = Mutable>> QueryData for &'__w mut T
where
for<'a> &'a mut T: crate::system::const_param_checking::AccessTreeContainer,
{
type ReadOnly = &'__w T;
type Item<'w> = Mut<'w, T>;

const COMPONENT_ACCESS_TREE_QUERY_DATA: ComponentAccessTree = Self::COMPONENT_ACCESS_TREE;

fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> {
item
}
Expand Down Expand Up @@ -1710,7 +1730,10 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> {
}

// SAFETY: access of `Ref<T>` is a subset of `Mut<T>`
unsafe impl<'__w, T: Component<Mutability = Mutable>> QueryData for Mut<'__w, T> {
unsafe impl<'__w, T: Component<Mutability = Mutable>> QueryData for Mut<'__w, T>
where
for<'a> &'a mut T: crate::system::const_param_checking::AccessTreeContainer,
{
type ReadOnly = Ref<'__w, T>;
type Item<'w> = Mut<'w, T>;

Expand Down Expand Up @@ -2052,6 +2075,8 @@ macro_rules! impl_tuple_query_data {
type ReadOnly = ($($name::ReadOnly,)*);
type Item<'w> = ($($name::Item<'w>,)*);

const COMPONENT_ACCESS_TREE_QUERY_DATA: ComponentAccessTree = impl_tuple_query_data!(@tree $($name),*);

fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
let ($($name,)*) = item;
($(
Expand All @@ -2076,6 +2101,36 @@ macro_rules! impl_tuple_query_data {
unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for ($($name,)*) {}

};

// Handle empty case
(@tree) => {
ComponentAccessTree {
this: ComponentAccess::Ignore,
left: None,
right: None,
}
};
// Handle single item case
(@tree $t0:ident) => {
$t0::COMPONENT_ACCESS_TREE_QUERY_DATA
};
// Handle two item case
(@tree $t0:ident, $t1:ident) => {
ComponentAccessTree::combine(
&$t0::COMPONENT_ACCESS_TREE_QUERY_DATA,
&$t1::COMPONENT_ACCESS_TREE_QUERY_DATA,
)
};
// Handle three or more items case
(@tree $t0:ident, $t1:ident, $($rest:ident),+) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need separate cases for two and three items? It seems like you could make the two-item case take (@tree $t0:ident, $($rest:ident),+) and do the induction there.

Alternately, I think you can do this without recursion. It might be simpler as something like

const COMPONENT_ACCESS_TREE_QUERY_DATA: ComponentAccessTree = const {
    let tree = ComponentAccessTree { this: ComponentAccess::Ignore, left: None, right: None };
    $(
        let tree = ComponentAccessTree::combine(tree, $name);
    )*
    tree
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 let tree = &ConstTreeInner::<ComponentAccess>::combine(tree, $name::COMPONENT_ACCESS_TREE_QUERY_DATA);
     |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |                                   |
     |                                   creates a temporary value which is freed while still in use
     |                                   argument requires that borrow lasts for `'static`

Sadly it seems like this doesn't work. But if you could elaborate on how to do the two item case instead I would like to switch to that!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see. I was expecting it to do static promotion... but that's only possible if the inner values are themselves const, and tree isn't const. And if you try to make a bunch of nested const TREE = then it complains that they "can't use generic parameters from outer item". Yuck.


But if you could elaborate on how to do the two item case instead I would like to switch to that!

The simplest change is

    (@tree) => {
        &ConstTreeInner::Empty
    };
    (@tree $t0:ident) => {
        $t0::COMPONENT_ACCESS_TREE_QUERY_DATA
    };
    (@tree $t0:ident, $($rest:ident),+) => {
        &ConstTreeInner::<ComponentAccess>::combine(
            $t0::COMPONENT_ACCESS_TREE_QUERY_DATA,
                impl_tuple_query_data!(@tree $($rest),+)
        )
    };

But you could go one further with

    // Base case
    (@tree) => {
        &ConstTreeInner::Empty
    };
    // Inductive case
    (@tree $t0:ident, $($rest:ident),*) => {
        &ConstTreeInner::<ComponentAccess>::combine(
            $t0::COMPONENT_ACCESS_TREE_QUERY_DATA,
            impl_tuple_query_data!(@tree $($rest),*)
        )
    };

for the cost of an extra combine(Empty).

... and for that matter, since we're already generating that same code for smaller tuples, you could use their trees rather than doing a second recursion:

    // Base case
    (@tree) => {
        &ConstTreeInner::Empty
    };
    // Inductive case
    (@tree $t0:ident, $($rest:ident),*) => {
        &ConstTreeInner::<ComponentAccess>::combine(
            $t0::COMPONENT_ACCESS_TREE_QUERY_DATA,
            <($($rest,)*)>::COMPONENT_ACCESS_TREE_QUERY_DATA
        )
    };

That should generate a little less code.

ComponentAccessTree::combine(
&$t0::COMPONENT_ACCESS_TREE_QUERY_DATA,
&ComponentAccessTree::combine(
&$t1::COMPONENT_ACCESS_TREE_QUERY_DATA,
&impl_tuple_query_data!(@tree $($rest),+)
)
)
};
}

macro_rules! impl_anytuple_fetch {
Expand Down
30 changes: 29 additions & 1 deletion crates/bevy_ecs/src/query/filter.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::system::const_param_checking::{WithId, WithoutId};
use crate::{
archetype::Archetype,
component::{Component, ComponentId, Components, StorageType, Tick},
Expand All @@ -6,6 +7,7 @@ use crate::{
storage::{ComponentSparseSet, Table, TableRow},
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
use bevy_ecs::system::const_param_checking::{WithFilterTree, WithoutFilterTree};
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use core::{cell::UnsafeCell, marker::PhantomData};
use variadics_please::all_tuples;
Expand Down Expand Up @@ -89,6 +91,9 @@ pub unsafe trait QueryFilter: WorldQuery {
/// If this is `true`, then [`QueryFilter::filter_fetch`] must always return true.
const IS_ARCHETYPAL: bool;

const WITH_FILTER_TREE_QUERY_DATA: Option<WithFilterTree> = None;
const WITHOUT_FILTER_TREE_QUERY_DATA: Option<WithoutFilterTree> = None;

/// Returns true if the provided [`Entity`] and [`TableRow`] should be included in the query results.
/// If false, the entity will be skipped.
///
Expand Down Expand Up @@ -202,6 +207,12 @@ unsafe impl<T: Component> WorldQuery for With<T> {
unsafe impl<T: Component> QueryFilter for With<T> {
const IS_ARCHETYPAL: bool = true;

const WITH_FILTER_TREE_QUERY_DATA: Option<WithFilterTree> = Some(WithFilterTree {
this: WithId(T::UNSTABLE_TYPE_ID),
left: None,
right: None,
});

#[inline(always)]
unsafe fn filter_fetch(
_fetch: &mut Self::Fetch<'_>,
Expand Down Expand Up @@ -302,6 +313,12 @@ unsafe impl<T: Component> WorldQuery for Without<T> {
unsafe impl<T: Component> QueryFilter for Without<T> {
const IS_ARCHETYPAL: bool = true;

const WITHOUT_FILTER_TREE_QUERY_DATA: Option<WithoutFilterTree> = Some(WithoutFilterTree {
this: WithoutId(T::UNSTABLE_TYPE_ID),
left: None,
right: None,
});

#[inline(always)]
unsafe fn filter_fetch(
_fetch: &mut Self::Fetch<'_>,
Expand Down Expand Up @@ -748,6 +765,13 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
// SAFETY: WorldQuery impl performs only read access on ticks
unsafe impl<T: Component> QueryFilter for Added<T> {
const IS_ARCHETYPAL: bool = false;

const WITH_FILTER_TREE_QUERY_DATA: Option<WithFilterTree> = Some(WithFilterTree {
this: WithId(T::UNSTABLE_TYPE_ID),
left: None,
right: None,
});

#[inline(always)]
unsafe fn filter_fetch(
fetch: &mut Self::Fetch<'_>,
Expand Down Expand Up @@ -975,7 +999,11 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
// SAFETY: WorldQuery impl performs only read access on ticks
unsafe impl<T: Component> QueryFilter for Changed<T> {
const IS_ARCHETYPAL: bool = false;

const WITH_FILTER_TREE_QUERY_DATA: Option<WithFilterTree> = Some(WithFilterTree {
this: WithId(T::UNSTABLE_TYPE_ID),
left: None,
right: None,
});
#[inline(always)]
unsafe fn filter_fetch(
fetch: &mut Self::Fetch<'_>,
Expand Down
Loading
Loading