diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 63083f5ed7f77..9ec9638b36a29 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -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 { diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index bd3d02d0308eb..4828682e854d3 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -11,7 +11,7 @@ categories = ["game-engines", "data-structures"] rust-version = "1.83.0" [features] -default = ["std", "bevy_reflect", "async_executor"] +default = ["std", "bevy_reflect", "async_executor", "diagnostic_component_names"] # Functionality @@ -53,6 +53,11 @@ bevy_debug_stepping = [] ## This will often provide more detailed error messages. track_location = [] +## Provides name information in Component traits for use in System conflict diagnostics +diagnostic_component_names = [ + "bevy_ecs_macros/diagnostic_component_names" +] + # Executor Backend ## Uses `async-executor` as a task execution backend. @@ -138,6 +143,7 @@ variadics_please = { version = "1.1", default-features = false } tracing = { version = "0.1", default-features = false, optional = true } log = { version = "0.4", default-features = false } bumpalo = "3" +const_panic = { version = "0.2.12" , features = ["rust_latest_stable"] } [dev-dependencies] rand = "0.8" diff --git a/crates/bevy_ecs/compile_fail/Cargo.toml b/crates/bevy_ecs/compile_fail/Cargo.toml index 76f7ec8b8aaa4..c9cc147b7d682 100644 --- a/crates/bevy_ecs/compile_fail/Cargo.toml +++ b/crates/bevy_ecs/compile_fail/Cargo.toml @@ -16,3 +16,7 @@ compile_fail_utils = { path = "../../../tools/compile_fail_utils" } [[test]] name = "ui" harness = false + +[[test]] +name = "system_params" +harness = false \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params.rs b/crates/bevy_ecs/compile_fail/tests/system_params.rs new file mode 100644 index 0000000000000..fc40a6acc2818 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params.rs @@ -0,0 +1,3 @@ +fn main() -> compile_fail_utils::ui_test::Result<()> { + compile_fail_utils::test("ecs_system_params", "tests/system_params") +} diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_conflicting.rs b/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_conflicting.rs new file mode 100644 index 0000000000000..b883eeb18b10e --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_conflicting.rs @@ -0,0 +1,9 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, bevy_ecs::query::AnyOf<(&mut A, &mut A)>>))), {closure@tests/system_params/any_of_with_conflicting.rs:8:37: 8:72}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +fn main() { + Schedule::default().add_systems(|_: Query>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_mut_and_option.rs b/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_mut_and_option.rs new file mode 100644 index 0000000000000..4ec84a5c197ac --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_mut_and_option.rs @@ -0,0 +1,9 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, bevy_ecs::query::AnyOf<(&mut A, std::option::Option<&A>)>>))), {closure@tests/system_params/any_of_with_mut_and_option.rs:8:37: 8:76}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +fn main() { + Schedule::default().add_systems(|_: Query)>>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_mut_and_ref.rs b/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_mut_and_ref.rs new file mode 100644 index 0000000000000..f35fb15d7b91d --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_mut_and_ref.rs @@ -0,0 +1,9 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, bevy_ecs::query::AnyOf<(&mut A, &A)>>))), {closure@tests/system_params/any_of_with_mut_and_ref.rs:8:37: 8:68}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +fn main() { + Schedule::default().add_systems(|_: Query>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_ref_and_mut.rs b/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_ref_and_mut.rs new file mode 100644 index 0000000000000..a4b3d6959b293 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/any_of_with_ref_and_mut.rs @@ -0,0 +1,9 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, bevy_ecs::query::AnyOf<(&A, &mut A)>>))), {closure@tests/system_params/any_of_with_ref_and_mut.rs:8:37: 8:68}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +fn main() { + Schedule::default().add_systems(|_: Query>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/changed_trackers_or_conflict.rs b/crates/bevy_ecs/compile_fail/tests/system_params/changed_trackers_or_conflict.rs new file mode 100644 index 0000000000000..f7c9d811907e9 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/changed_trackers_or_conflict.rs @@ -0,0 +1,21 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, &A>, bevy_ecs::system::Query<'_, '_, &mut A>))), {closure@tests/system_params/conflicting_query_immut_system.rs:20:37: 20:69}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +#[derive(Component)] +pub struct B; + +#[derive(Component)] +pub struct C; + +#[derive(Component)] +pub struct D; + +#[derive(Component)] +pub struct E; + +fn main() { + Schedule::default().add_systems(|_: Query<&mut A>, _: Query<(), Or<(Changed,)>>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/conflicting_query_immut_system.rs b/crates/bevy_ecs/compile_fail/tests/system_params/conflicting_query_immut_system.rs new file mode 100644 index 0000000000000..7bbaa53da6ebe --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/conflicting_query_immut_system.rs @@ -0,0 +1,21 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, &A>, bevy_ecs::system::Query<'_, '_, &mut A>))), {closure@tests/system_params/conflicting_query_immut_system.rs:20:37: 20:69}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +#[derive(Component)] +pub struct B; + +#[derive(Component)] +pub struct C; + +#[derive(Component)] +pub struct D; + +#[derive(Component)] +pub struct E; + +fn main() { + Schedule::default().add_systems(|_: Query<&A>, _: Query<&mut A>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/conflicting_query_mut_system.rs b/crates/bevy_ecs/compile_fail/tests/system_params/conflicting_query_mut_system.rs new file mode 100644 index 0000000000000..c778868fa2308 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/conflicting_query_mut_system.rs @@ -0,0 +1,21 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, &mut A>, bevy_ecs::system::Query<'_, '_, &mut A>))), {closure@tests/system_params/conflicting_query_mut_system.rs:20:37: 20:73}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +#[derive(Component)] +pub struct B; + +#[derive(Component)] +pub struct C; + +#[derive(Component)] +pub struct D; + +#[derive(Component)] +pub struct E; + +fn main() { + Schedule::default().add_systems(|_: Query<&mut A>, _: Query<&mut A>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_nested_or_with_and_disjoint_without.rs b/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_nested_or_with_and_disjoint_without.rs new file mode 100644 index 0000000000000..34815f94d1e72 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_nested_or_with_and_disjoint_without.rs @@ -0,0 +1,24 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, &mut D, bevy_ecs::query::Or<(bevy_ecs::query::Or<(bevy_ecs::query::With, bevy_ecs::query::With)>, bevy_ecs::query::Or<(bevy_ecs::query::With, bevy_ecs::query::With)>)>>, bevy_ecs::system::Query<'_, '_, &mut D, bevy_ecs::query::Without>))), {closure@tests/system_params/or_expanded_nested_or_with_and_disjoint_without.rs:20:37: 23:6}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +#[derive(Component)] +pub struct B; + +#[derive(Component)] +pub struct C; + +#[derive(Component)] +pub struct D; + +#[derive(Component)] +pub struct E; + +fn main() { + Schedule::default().add_systems(| + _: Query<&mut D, Or<(Or<(With, With)>, Or<(With, With)>)>>, + _: Query<&mut D, Without>, + | {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_nested_with_and_disjoint_nested_without.rs b/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_nested_with_and_disjoint_nested_without.rs new file mode 100644 index 0000000000000..c862ddbe0f14d --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_nested_with_and_disjoint_nested_without.rs @@ -0,0 +1,24 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, &mut D, bevy_ecs::query::Or<((bevy_ecs::query::With, bevy_ecs::query::With), (bevy_ecs::query::With, bevy_ecs::query::With))>>, bevy_ecs::system::Query<'_, '_, &mut D, bevy_ecs::query::Or<(bevy_ecs::query::Without, bevy_ecs::query::Without)>>))), {closure@tests/system_params/or_expanded_nested_with_and_disjoint_nested_without.rs:20:37: 23:6}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +#[derive(Component)] +pub struct B; + +#[derive(Component)] +pub struct C; + +#[derive(Component)] +pub struct D; + +#[derive(Component)] +pub struct E; + +fn main() { + Schedule::default().add_systems(| + _: Query<&mut D, Or<((With, With), (With, With))>>, + _: Query<&mut D, Or<(Without, Without)>>, + | {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_nested_with_and_disjoint_without.rs b/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_nested_with_and_disjoint_without.rs new file mode 100644 index 0000000000000..658c52996f08e --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_nested_with_and_disjoint_without.rs @@ -0,0 +1,23 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, &mut E, (bevy_ecs::query::Or<((bevy_ecs::query::With, bevy_ecs::query::With), (bevy_ecs::query::With, bevy_ecs::query::With))>, bevy_ecs::query::With)>, bevy_ecs::system::Query<'_, '_, &mut E, bevy_ecs::query::Without>))), {closure@tests/system_params/or_expanded_nested_with_and_disjoint_without.rs:20:37: 22:38}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +#[derive(Component)] +pub struct B; + +#[derive(Component)] +pub struct C; + +#[derive(Component)] +pub struct D; + +#[derive(Component)] +pub struct E; + +fn main() { + Schedule::default().add_systems(| + _: Query<&mut E, (Or<((With, With), (With, With))>, With)>, + _: Query<&mut E, Without>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_with_and_disjoint_nested_without.rs b/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_with_and_disjoint_nested_without.rs new file mode 100644 index 0000000000000..8330e923241c8 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/or_expanded_with_and_disjoint_nested_without.rs @@ -0,0 +1,24 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, &mut D, bevy_ecs::query::Or<(bevy_ecs::query::With, bevy_ecs::query::With)>>, bevy_ecs::system::Query<'_, '_, &mut D, bevy_ecs::query::Or<(bevy_ecs::query::Without, bevy_ecs::query::Without)>>))), {closure@tests/system_params/or_expanded_with_and_disjoint_nested_without.rs:20:37: 23:6}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +#[derive(Component)] +pub struct B; + +#[derive(Component)] +pub struct C; + +#[derive(Component)] +pub struct D; + +#[derive(Component)] +pub struct E; + +fn main() { + Schedule::default().add_systems(| + _: Query<&mut D, Or<(With, With)>>, + _: Query<&mut D, Or<(Without, Without)>>, + | {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/or_has_no_filter_with.rs b/crates/bevy_ecs/compile_fail/tests/system_params/or_has_no_filter_with.rs new file mode 100644 index 0000000000000..57509727a9250 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/or_has_no_filter_with.rs @@ -0,0 +1,12 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, &mut B, bevy_ecs::query::Or<(bevy_ecs::query::With, bevy_ecs::query::With)>>, bevy_ecs::system::Query<'_, '_, &mut B, bevy_ecs::query::Without>))), {closure@tests/system_params/or_has_no_filter_with.rs:11:37: 11:109}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +#[derive(Component)] +pub struct B; + +fn main() { + Schedule::default().add_systems(|_: Query<&mut B, Or<(With, With)>>, _: Query<&mut B, Without>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/system_params/with_and_disjoint_or_empty_without.rs b/crates/bevy_ecs/compile_fail/tests/system_params/with_and_disjoint_or_empty_without.rs new file mode 100644 index 0000000000000..c52cf0d52d684 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/system_params/with_and_disjoint_or_empty_without.rs @@ -0,0 +1,21 @@ +//@error-in-other-file: evaluation of `bevy_ecs::schedule::Schedule::add_systems::<(bevy_ecs::schedule::Infallible, (bevy_ecs::system::IsFunctionSystem, fn(bevy_ecs::system::Query<'_, '_, &mut B, bevy_ecs::query::With>, bevy_ecs::system::Query<'_, '_, &mut B, bevy_ecs::query::Or<((), bevy_ecs::query::Without)>>))), {closure@tests/system_params/with_and_disjoint_or_empty_without.rs:20:37: 20:104}>::{constant#0}` failed +use bevy_ecs::prelude::*; + +#[derive(Component)] +pub struct A; + +#[derive(Component)] +pub struct B; + +#[derive(Component)] +pub struct C; + +#[derive(Component)] +pub struct D; + +#[derive(Component)] +pub struct E; + +fn main() { + Schedule::default().add_systems(|_: Query<&mut B, With>, _: Query<&mut B, Or<((), Without)>>| {}); +} \ No newline at end of file diff --git a/crates/bevy_ecs/examples/change_detection.rs b/crates/bevy_ecs/examples/change_detection.rs index 23420b5e88038..b610fcac66c2c 100644 --- a/crates/bevy_ecs/examples/change_detection.rs +++ b/crates/bevy_ecs/examples/change_detection.rs @@ -15,6 +15,12 @@ use bevy_ecs::prelude::*; use rand::Rng; use std::ops::Deref; +#[derive(Component)] +pub struct B; + +#[derive(Component)] +pub struct A; + fn main() { // Create a new empty World to hold our Entities, Components and Resources let mut world = World::new(); @@ -28,6 +34,7 @@ fn main() { // Add systems to the Schedule to execute our app logic // We can label our systems to force a specific run-order between some of them schedule.add_systems(( + |_: Query<&mut A>, _: Query<(), Or<(Changed,)>>|{}, spawn_entities.in_set(SimulationSet::Spawn), print_counter_when_changed.after(SimulationSet::Spawn), age_all_entities.in_set(SimulationSet::Age), diff --git a/crates/bevy_ecs/macros/Cargo.toml b/crates/bevy_ecs/macros/Cargo.toml index f1ea54894a1bb..a07c0265456de 100644 --- a/crates/bevy_ecs/macros/Cargo.toml +++ b/crates/bevy_ecs/macros/Cargo.toml @@ -8,12 +8,16 @@ license = "MIT OR Apache-2.0" [lib] proc-macro = true +[features] +diagnostic_component_names = [] + [dependencies] 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 diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 76732c0b895ac..20f08b2293472 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -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, @@ -214,12 +215,24 @@ pub fn derive_component(input: TokenStream) -> TokenStream { (&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::::default()).default_clone_behavior() ) }; + let mut rng = rand::rng(); + let unstable_type_id: u128 = rng.random(); + let struct_name_2 = struct_name.to_string(); + let struct_name_code = if cfg!(feature = "diagnostic_component_names") { + quote! { + const STRUCT_NAME: &'static str = #struct_name_2; + } + } else { + quote! {} + }; // 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; + #struct_name_code type Mutability = #mutable_type; fn register_required_components( requiree: #bevy_ecs_path::component::ComponentId, diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 0eaa329c61a30..2d478f611622d 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -386,6 +386,18 @@ pub trait Component: Send + Sync + 'static { /// A constant indicating the storage type used for this component. const STORAGE_TYPE: StorageType; + /// A randomly generated u128 that is consistent per component type, used to uniquely identify + /// components in const contexts. + /// This enables compile-time validation of component access patterns + /// for both inter-query and intra-query correctness. + const UNSTABLE_TYPE_ID: u128; + + /// The name of the component's struct, if available. + /// This is used to provide more helpful error messages when component access conflicts + /// are detected at compile-time. Will be None if the component isn't derived automatically + /// or if the name isn't available. + #[cfg(feature = "diagnostic_component_names")] + const STRUCT_NAME: &'static str = ""; /// A marker type to assist Bevy with determining if this component is /// mutable, or immutable. Mutable components will have [`Component`], /// while immutable components will instead have [`Component`]. diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 556ca0d8aa523..5a549354113d3 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -15,6 +15,7 @@ pub struct ObservedBy(pub(crate) Vec); impl Component for ObservedBy { const STORAGE_TYPE: StorageType = StorageType::SparseSet; + const UNSTABLE_TYPE_ID: u128 = 1; type Mutability = Mutable; fn on_remove() -> Option { diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index fdb3b4fa800ba..cf0a470c7d0c3 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -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 { @@ -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 { Some(|world, context| { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 08e06d03f4478..58cc09403066c 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,3 +1,6 @@ +use crate::system::const_param_checking::{ + AccessTreeContainer, ComponentAccess, ConstTree, ConstTreeInner, +}; use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundle, @@ -15,6 +18,8 @@ use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; use core::{cell::UnsafeCell, marker::PhantomData, panic::Location}; use smallvec::SmallVec; use variadics_please::all_tuples; +use bevy_ecs::system::const_param_checking::constime; +use bevy_ecs::system::const_param_checking::constime::FilteredAccessHolder; /// Types that can be fetched from a [`World`] using a [`Query`]. /// @@ -284,6 +289,15 @@ pub unsafe trait QueryData: WorldQuery { /// and is visible to the end user when calling e.g. `Query::get`. type Item<'a>; + /// A compile-time representation of how this query accesses components. + /// Used to validate query compatibility and detect conflicting access patterns + /// during const evaluation. + /// By default it implements an Ignore, and can be overrided by implementations such as + /// impl or impl + /// There we recursively build up this component access tree, checking for intra-query + /// errors along the way. + const COMPONENT_ACCESS_TREE_QUERY_DATA: ConstTree = &ConstTreeInner::Empty; + /// This function manually implements subtyping for the query items. fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort>; @@ -1111,6 +1125,8 @@ unsafe impl WorldQuery for &T { type Fetch<'w> = ReadFetch<'w, T>; type State = ComponentId; + const FILTERED_ACCESS: &'static constime::FilteredAccess = ::FILTERED_ACCESS; + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1203,10 +1219,16 @@ unsafe impl WorldQuery for &T { } /// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl QueryData for &T { +unsafe impl QueryData for &T +where + for<'a> &'a T: AccessTreeContainer, +{ type ReadOnly = Self; type Item<'w> = &'w T; + const COMPONENT_ACCESS_TREE_QUERY_DATA: ConstTree = + Self::COMPONENT_ACCESS_TREE; + fn shrink<'wlong: 'wshort, 'wshort>(item: &'wlong T) -> &'wshort T { item } @@ -1471,6 +1493,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { type Fetch<'w> = WriteFetch<'w, T>; type State = ComponentId; + const FILTERED_ACCESS: &'static constime::FilteredAccess = ::FILTERED_ACCESS; + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1568,10 +1592,16 @@ 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> QueryData for &'__w mut T { +unsafe impl<'__w, T: Component> QueryData for &'__w mut T +where + for<'a> &'a mut T: AccessTreeContainer, +{ type ReadOnly = &'__w T; type Item<'w> = Mut<'w, T>; + const COMPONENT_ACCESS_TREE_QUERY_DATA: ConstTree = + Self::COMPONENT_ACCESS_TREE; + fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> { item } @@ -1710,7 +1740,10 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { } // SAFETY: access of `Ref` is a subset of `Mut` -unsafe impl<'__w, T: Component> QueryData for Mut<'__w, T> { +unsafe impl<'__w, T: Component> QueryData for Mut<'__w, T> +where + for<'a> &'a mut T: AccessTreeContainer, +{ type ReadOnly = Ref<'__w, T>; type Item<'w> = Mut<'w, T>; @@ -1841,6 +1874,9 @@ unsafe impl QueryData for Option { type ReadOnly = Option; type Item<'w> = Option>; + const COMPONENT_ACCESS_TREE_QUERY_DATA: ConstTree = + T::COMPONENT_ACCESS_TREE_QUERY_DATA; + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { item.map(T::shrink) } @@ -2052,6 +2088,8 @@ macro_rules! impl_tuple_query_data { type ReadOnly = ($($name::ReadOnly,)*); type Item<'w> = ($($name::Item<'w>,)*); + const COMPONENT_ACCESS_TREE_QUERY_DATA: ConstTree = impl_tuple_query_data!(@tree $($name),*); + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { let ($($name,)*) = item; ($( @@ -2076,6 +2114,32 @@ macro_rules! impl_tuple_query_data { unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for ($($name,)*) {} }; + + // Handle empty case + (@tree) => { + &ConstTreeInner::Empty + }; + // Handle single item case + (@tree $t0:ident) => { + $t0::COMPONENT_ACCESS_TREE_QUERY_DATA + }; + // Handle two item case + (@tree $t0:ident, $t1:ident) => { + &ConstTreeInner::::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),+) => { + &ConstTreeInner::::combine( + $t0::COMPONENT_ACCESS_TREE_QUERY_DATA, + &ConstTreeInner::::combine( + $t1::COMPONENT_ACCESS_TREE_QUERY_DATA, + impl_tuple_query_data!(@tree $($rest),+) + ) + ) + }; } macro_rules! impl_anytuple_fetch { @@ -2214,6 +2278,8 @@ macro_rules! impl_anytuple_fetch { type ReadOnly = AnyOf<($($name::ReadOnly,)*)>; type Item<'w> = ($(Option<$name::Item<'w>>,)*); + const COMPONENT_ACCESS_TREE_QUERY_DATA: ConstTree = impl_tuple_query_data!(@tree $($name),*); + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { let ($($name,)*) = item; ($( @@ -2239,6 +2305,32 @@ macro_rules! impl_anytuple_fetch { /// SAFETY: each item in the tuple is read only unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for AnyOf<($($name,)*)> {} }; + + // Handle empty case + (@tree) => { + &ConstTreeInner::Empty + }; + // Handle single item case + (@tree $t0:ident) => { + $t0::COMPONENT_ACCESS_TREE_QUERY_DATA + }; + // Handle two item case + (@tree $t0:ident, $t1:ident) => { + &ConstTreeInner::::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),+) => { + &ConstTreeInner::::combine( + $t0::COMPONENT_ACCESS_TREE_QUERY_DATA, + &ConstTreeInner::::combine( + $t1::COMPONENT_ACCESS_TREE_QUERY_DATA, + impl_anytuple_fetch!(@tree $($rest),+) + ) + ) + }; } all_tuples!( diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 9e4f36a9655f6..6a9cfc1ada8d6 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -1,3 +1,4 @@ +use crate::system::const_param_checking::{constime, ConstTree, ConstTreeInner, WithId, WithoutId}; use crate::{ archetype::Archetype, component::{Component, ComponentId, Components, StorageType, Tick}, @@ -9,6 +10,8 @@ use crate::{ use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; use core::{cell::UnsafeCell, marker::PhantomData}; use variadics_please::all_tuples; +use bevy_ecs::system::const_param_checking::{AccessType, ComponentAccess}; +use crate::system::const_param_checking::constime::FilteredAccessHolder; /// Types that filter the results of a [`Query`]. /// @@ -89,6 +92,18 @@ pub unsafe trait QueryFilter: WorldQuery { /// If this is `true`, then [`QueryFilter::filter_fetch`] must always return true. const IS_ARCHETYPAL: bool; + /// A compile-time representation of the `With`/`Added`/`Changed` filters applied to this query + /// Used for validating query compatibility during const evaluation + const WITH_FILTER_TREE_QUERY_DATA: ConstTree = &ConstTreeInner::Empty; + + /// A compile-time representation of the Without filters applied to this query + /// Used for validating query compatibility during const evaluation + const WITHOUT_FILTER_TREE_QUERY_DATA: ConstTree = &ConstTreeInner::Empty; + + /// A compile-time representation of the `Added` and `Changed` filters applied to this + /// query. Used for validating query compatibility during const evaluation. + const COMPONENT_ACCESS_TREE_QUERY_DATA: ConstTree = &ConstTreeInner::Empty; + /// Returns true if the provided [`Entity`] and [`TableRow`] should be included in the query results. /// If false, the entity will be skipped. /// @@ -147,6 +162,8 @@ unsafe impl WorldQuery for With { type Fetch<'w> = (); type State = ComponentId; + const FILTERED_ACCESS: &'static constime::FilteredAccess = ::FILTERED_ACCESS; + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} #[inline] @@ -202,6 +219,9 @@ unsafe impl WorldQuery for With { unsafe impl QueryFilter for With { const IS_ARCHETYPAL: bool = true; + const WITH_FILTER_TREE_QUERY_DATA: ConstTree = + &ConstTreeInner::Leaf(WithId(T::UNSTABLE_TYPE_ID)); + #[inline(always)] unsafe fn filter_fetch( _fetch: &mut Self::Fetch<'_>, @@ -246,6 +266,7 @@ pub struct Without(PhantomData); unsafe impl WorldQuery for Without { type Fetch<'w> = (); type State = ComponentId; + const FILTERED_ACCESS: &'static constime::FilteredAccess = ::FILTERED_ACCESS; fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} @@ -302,6 +323,9 @@ unsafe impl WorldQuery for Without { unsafe impl QueryFilter for Without { const IS_ARCHETYPAL: bool = true; + const WITHOUT_FILTER_TREE_QUERY_DATA: ConstTree = + &ConstTreeInner::Leaf(WithoutId(T::UNSTABLE_TYPE_ID)); + #[inline(always)] unsafe fn filter_fetch( _fetch: &mut Self::Fetch<'_>, @@ -493,6 +517,9 @@ macro_rules! impl_or_query_filter { unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; + const WITHOUT_FILTER_TREE_QUERY_DATA: ConstTree = impl_or_query_filter!(@without_tree $($filter),*); + //const WITH_FILTER_TREE_QUERY_DATA: ConstTree = impl_or_query_filter!(@with_tree $($filter),*); + #[inline(always)] unsafe fn filter_fetch( fetch: &mut Self::Fetch<'_>, @@ -505,6 +532,56 @@ macro_rules! impl_or_query_filter { } } }; + // Handle empty case for WITHOUT_FILTER_TREE + (@without_tree) => { + &ConstTreeInner::Empty + }; + // Handle single item case for WITHOUT_FILTER_TREE + (@without_tree $t0:ident) => { + $t0::WITHOUT_FILTER_TREE_QUERY_DATA + }; + // Handle two item case for WITHOUT_FILTER_TREE + (@without_tree $t0:ident, $t1:ident) => { + &ConstTreeInner::::combine( + $t0::WITHOUT_FILTER_TREE_QUERY_DATA, + $t1::WITHOUT_FILTER_TREE_QUERY_DATA, + ) + }; + // Handle three or more items case for WITHOUT_FILTER_TREE + (@without_tree $t0:ident, $t1:ident, $($rest:ident),+) => { + &ConstTreeInner::::combine( + $t0::WITHOUT_FILTER_TREE_QUERY_DATA, + &ConstTreeInner::::combine( + $t1::WITHOUT_FILTER_TREE_QUERY_DATA, + impl_tuple_query_filter!(@without_tree $($rest),+) + ) + ) + }; + // Handle empty case for WITH_FILTER_TREE + (@with_tree) => { + &ConstTreeInner::Empty + }; + // Handle single item case for WITH_FILTER_TREE + (@with_tree $t0:ident) => { + $t0::WITH_FILTER_TREE_QUERY_DATA + }; + // Handle two item case for WITH_FILTER_TREE + (@with_tree $t0:ident, $t1:ident) => { + &ConstTreeInner::::combine( + $t0::WITH_FILTER_TREE_QUERY_DATA, + $t1::WITH_FILTER_TREE_QUERY_DATA, + ) + }; + // Handle three or more items case for WITH_FILTER_TREE + (@with_tree $t0:ident, $t1:ident, $($rest:ident),+) => { + &ConstTreeInner::::combine( + $t0::WITH_FILTER_TREE_QUERY_DATA, + &ConstTreeInner::::combine( + $t1::WITH_FILTER_TREE_QUERY_DATA, + impl_tuple_query_filter!(@with_tree $($rest),+) + ) + ) + }; } macro_rules! impl_tuple_query_filter { @@ -526,6 +603,9 @@ macro_rules! impl_tuple_query_filter { unsafe impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; + const WITHOUT_FILTER_TREE_QUERY_DATA: ConstTree = impl_tuple_query_filter!(@without_tree $($name),*); + const WITH_FILTER_TREE_QUERY_DATA: ConstTree = impl_tuple_query_filter!(@with_tree $($name),*); + #[inline(always)] unsafe fn filter_fetch( fetch: &mut Self::Fetch<'_>, @@ -537,7 +617,56 @@ macro_rules! impl_tuple_query_filter { true $(&& unsafe { $name::filter_fetch($name, entity, table_row) })* } } - + }; + // Handle empty case for WITHOUT_FILTER_TREE + (@without_tree) => { + &ConstTreeInner::Empty + }; + // Handle single item case for WITHOUT_FILTER_TREE + (@without_tree $t0:ident) => { + $t0::WITHOUT_FILTER_TREE_QUERY_DATA + }; + // Handle two item case for WITHOUT_FILTER_TREE + (@without_tree $t0:ident, $t1:ident) => { + &ConstTreeInner::::combine( + $t0::WITHOUT_FILTER_TREE_QUERY_DATA, + $t1::WITHOUT_FILTER_TREE_QUERY_DATA, + ) + }; + // Handle three or more items case for WITHOUT_FILTER_TREE + (@without_tree $t0:ident, $t1:ident, $($rest:ident),+) => { + &ConstTreeInner::::combine( + $t0::WITHOUT_FILTER_TREE_QUERY_DATA, + &ConstTreeInner::::combine( + $t1::WITHOUT_FILTER_TREE_QUERY_DATA, + impl_tuple_query_filter!(@without_tree $($rest),+) + ) + ) + }; + // Handle empty case for WITH_FILTER_TREE + (@with_tree) => { + &ConstTreeInner::Empty + }; + // Handle single item case for WITH_FILTER_TREE + (@with_tree $t0:ident) => { + $t0::WITH_FILTER_TREE_QUERY_DATA + }; + // Handle two item case for WITH_FILTER_TREE + (@with_tree $t0:ident, $t1:ident) => { + &ConstTreeInner::::combine( + $t0::WITH_FILTER_TREE_QUERY_DATA, + $t1::WITH_FILTER_TREE_QUERY_DATA, + ) + }; + // Handle three or more items case for WITH_FILTER_TREE + (@with_tree $t0:ident, $t1:ident, $($rest:ident),+) => { + &ConstTreeInner::::combine( + $t0::WITH_FILTER_TREE_QUERY_DATA, + &ConstTreeInner::::combine( + $t1::WITH_FILTER_TREE_QUERY_DATA, + impl_tuple_query_filter!(@with_tree $($rest),+) + ) + ) }; } @@ -748,6 +877,10 @@ unsafe impl WorldQuery for Added { // SAFETY: WorldQuery impl performs only read access on ticks unsafe impl QueryFilter for Added { const IS_ARCHETYPAL: bool = false; + + const WITH_FILTER_TREE_QUERY_DATA: ConstTree = + &ConstTreeInner::Leaf(WithId(T::UNSTABLE_TYPE_ID)); + #[inline(always)] unsafe fn filter_fetch( fetch: &mut Self::Fetch<'_>, @@ -975,7 +1108,14 @@ unsafe impl WorldQuery for Changed { // SAFETY: WorldQuery impl performs only read access on ticks unsafe impl QueryFilter for Changed { const IS_ARCHETYPAL: bool = false; - + const WITH_FILTER_TREE_QUERY_DATA: ConstTree = + &ConstTreeInner::Leaf(WithId(T::UNSTABLE_TYPE_ID)); + + const COMPONENT_ACCESS_TREE_QUERY_DATA: ConstTree = &ConstTreeInner::Leaf(ComponentAccess { + type_id: T::UNSTABLE_TYPE_ID, + access: AccessType::Mut, + name: T::STRUCT_NAME, + }); #[inline(always)] unsafe fn filter_fetch( fetch: &mut Self::Fetch<'_>, diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index da147770e0fcf..86513a81bc25e 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -6,6 +6,7 @@ use crate::{ world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use variadics_please::all_tuples; +use crate::system::const_param_checking::constime; /// Types that can be used as parameters in a [`Query`]. /// Types that implement this should also implement either [`QueryData`] or [`QueryFilter`] @@ -47,6 +48,8 @@ pub unsafe trait WorldQuery { /// constructing [`Self::Fetch`](WorldQuery::Fetch). type State: Send + Sync + Sized; + const FILTERED_ACCESS: &'static constime::FilteredAccess = &constime::FilteredAccess::Empty; + /// This function manually implements subtyping for the query fetches. fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort>; diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 898cf674245d2..d22d821da22d9 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -1,6 +1,7 @@ use alloc::{boxed::Box, vec, vec::Vec}; use variadics_please::all_tuples; +use crate::system::const_param_checking::SystemPanicMessage; use crate::{ result::Result, schedule::{ @@ -290,6 +291,10 @@ pub trait IntoSystemConfigs where Self: Sized, { + /// Compile-time error checker for system configurations + /// Contains validation results from checking parameter compatibility in system configs and + /// if there is a resulting panic message, trickles it up the stack. + const INTO_SYSTEM_CONFIGS_PANIC_CHECKER: Option = None; /// Convert into a [`SystemConfigs`]. fn into_configs(self) -> SystemConfigs; @@ -534,6 +539,8 @@ impl IntoSystemConfigs<(Infallible, Marker)> for F where F: IntoSystem<(), (), Marker>, { + const INTO_SYSTEM_CONFIGS_PANIC_CHECKER: Option = + F::INTO_SYSTEM_PANIC_CHECKER; fn into_configs(self) -> SystemConfigs { let wrapper = InfallibleSystemWrapper::new(IntoSystem::into_system(self)); SystemConfigs::new_system(Box::new(wrapper)) @@ -548,6 +555,8 @@ impl IntoSystemConfigs<(Fallible, Marker)> for F where F: IntoSystem<(), Result, Marker>, { + const INTO_SYSTEM_CONFIGS_PANIC_CHECKER: Option = + F::INTO_SYSTEM_PANIC_CHECKER; fn into_configs(self) -> SystemConfigs { let boxed_system = Box::new(IntoSystem::into_system(self)); SystemConfigs::new_system(boxed_system) @@ -570,6 +579,15 @@ macro_rules! impl_system_collection { where $($sys: IntoSystemConfigs<$param>),* { + const INTO_SYSTEM_CONFIGS_PANIC_CHECKER: Option = const { + let mut val = None; + $(if let Some(awa) = $sys::INTO_SYSTEM_CONFIGS_PANIC_CHECKER { + val = Some(awa); + + })* + val + }; + #[expect( clippy::allow_attributes, reason = "We are inside a macro, and as such, `non_snake_case` is not guaranteed to apply." diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index a613a8745fb9e..e4825781830c6 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -2,6 +2,15 @@ clippy::module_inception, reason = "This instance of module inception is being discussed; see #17344." )] +use crate::{ + component::{ComponentId, Components, Tick}, + prelude::Component, + resource::Resource, + result::{DefaultSystemErrorHandler, Error, SystemErrorContext}, + schedule::*, + system::ScheduleSystem, + world::World, +}; use alloc::{ boxed::Box, collections::{BTreeMap, BTreeSet}, @@ -10,6 +19,7 @@ use alloc::{ vec, vec::Vec, }; +use bevy_ecs::system::const_param_checking::AccessType; use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_utils::{default, TypeIdMap}; use core::{ @@ -24,16 +34,6 @@ use thiserror::Error; #[cfg(feature = "trace")] use tracing::info_span; -use crate::{ - component::{ComponentId, Components, Tick}, - prelude::Component, - resource::Resource, - result::{DefaultSystemErrorHandler, Error, SystemErrorContext}, - schedule::*, - system::ScheduleSystem, - world::World, -}; - use crate::{query::AccessConflicts, storage::SparseSetIndex}; pub use stepping::Stepping; use Direction::{Incoming, Outgoing}; @@ -334,7 +334,28 @@ impl Schedule { } /// Add a collection of systems to the schedule. - pub fn add_systems(&mut self, systems: impl IntoSystemConfigs) -> &mut Self { + pub fn add_systems>( + &mut self, + systems: IntoSystemConfig, + ) -> &mut Self { + const { + match IntoSystemConfig::INTO_SYSTEM_CONFIGS_PANIC_CHECKER { + None => {} + Some(owo) => { + let lhs_access_type = match owo.lhs_access_type { + AccessType::Ref => "&", + AccessType::Mut => "&mut", + }; + let rhs_access_type = match owo.rhs_access_type { + AccessType::Ref => "&", + AccessType::Mut => "&mut", + }; + let lhs_name = owo.lhs_name; + let rhs_name = owo.rhs_name; + const_panic::concat_panic!(const_panic::FmtArg::DISPLAY; "\nInvalid System Queries, ", lhs_access_type," ", lhs_name, " conflicts with ", rhs_access_type," ", rhs_name, " in the same system\n"); + } + } + }; self.graph.process_configs(systems.into_configs(), false); self } diff --git a/crates/bevy_ecs/src/system/const_param_checking.rs b/crates/bevy_ecs/src/system/const_param_checking.rs new file mode 100644 index 0000000000000..53f01c3e2ea15 --- /dev/null +++ b/crates/bevy_ecs/src/system/const_param_checking.rs @@ -0,0 +1,428 @@ +use bevy_ecs::component::Component; +use bevy_ecs::system::SystemParam; +use core::fmt::Debug; +use variadics_please::all_tuples; +use bevy_ecs::query::QueryFilter; + +/// A message describing a system parameter validation error that occurred during const evaluation. +/// Contains information about the conflicting parameter access types and their names for +/// displaying helpful error messages to users. +#[derive(Copy, Clone, Debug)] +pub struct SystemPanicMessage { + /// The type of access (Ref/Mut) for the left-hand side component in a conflict + pub lhs_access_type: AccessType, + /// The name of the left-hand side component involved in the conflict + pub lhs_name: &'static str, + /// The type of access (Ref/Mut) for the right-hand side component in a conflict + pub rhs_access_type: AccessType, + /// The name of the right-hand side component involved in the conflict + pub rhs_name: &'static str, +} + +/// Describes how a component is accessed within a system - either ignored +/// or used with a specific access type (Ref/Mut) and optional name. +#[derive(Copy, Clone, Debug)] +pub struct ComponentAccess { + /// Unique compile-time identifier for the component type + pub type_id: u128, + /// The type of access (reference or mutable) to the component + pub access: AccessType, + /// The component's name if available, used for error reporting + pub name: &'static str, +} +impl ComponentAccess { + const fn conflicts(&self, rhs: &ComponentAccess) -> Option { + if self.type_id != rhs.type_id { + return None; + } + match (self.access, rhs.access) { + (AccessType::Mut, _) | (_, AccessType::Mut) => { + //panic!("OwO"); + Some(SystemPanicMessage { + lhs_access_type: self.access, + lhs_name: self.name, + rhs_access_type: rhs.access, + rhs_name: rhs.name, + }) + } + _ => None, + } + } +} +/// The type of access to a component - either read-only reference or mutable reference. +#[derive(Copy, Clone, Debug)] +pub enum AccessType { + /// Immutable reference access (&T) + Ref, + /// Mutable reference access (&mut T) + Mut, +} + +impl AccessType { + /// String representation of AccessType::Ref + const REF_STR: &'static str = "&"; + /// String representation of AccessType::Mut + const MUT_STR: &'static str = "&mut"; + /// Converts to String representation of AccessType. + pub fn as_str(&self) -> &'static str { + match self { + AccessType::Ref => Self::REF_STR, + AccessType::Mut => Self::MUT_STR, + } + } +} + +/// CONST_UNSTABLE_TYPEID for a component within a Without<> filter +#[derive(Clone, Copy, Debug)] +pub struct WithoutId(pub u128); +/// CONST_UNSTABLE_TYPEID for a component within a With<>, Added<>, and Changed<> filter +#[derive(Clone, Copy, Debug)] +pub struct WithId(pub u128); + +/// An unordered tree created at compile time. +/// The reason it's a tree instead of a list is because we are unable to construct an arbitrarily +/// sized nil cons list in stable rust with the data we want. +#[derive(Copy, Clone, Debug)] +pub enum ConstTreeInner { + /// The tree is empty + Empty, + /// Leaf node + Leaf(T), + /// Unordered list as a tree because we have no choice + Node(ConstTree, ConstTree), + /// Panic message that gets bubbled up + PanicMessage(SystemPanicMessage), +} +/// For cleaner code, the only version of a `ConstTreeInner` that will ever actually exist +/// will be a `&'static ConstTreeInner`; so we give it an easy name. +pub type ConstTree = &'static ConstTreeInner; + +/// A collection of all the ConstTrees in a system parameter. +pub struct ConstTrees { + /// All `Component` accesses in queries. + pub component_tree: ConstTree, + /// All `Without` + pub without_tree: ConstTree, + /// All `With`/`Added`/`Changed`/`Option` etc. + pub with_tree: ConstTree, +} + +/// Checks a system parameter for a conflicts using recursive const data structures. +pub const fn check_system_parameters_for_conflicts( + left: ConstTrees, + right: ConstTrees, +) -> Option { + // First thing we do is return any panic messages, these should only exist on the component trees + // But later we might want some sort of check to ensure we aren't doubling up on filters in the filters. + if let ConstTreeInner::PanicMessage(panic_message) = left.component_tree { + return Some(*panic_message); + } + if let ConstTreeInner::PanicMessage(panic_message) = right.component_tree { + return Some(*panic_message); + } + + // REGION: We check for any intersections between components we require and components we anti-require. + if right.component_tree.intersects(left.without_tree) { + return None; + } + if left.component_tree.intersects(right.without_tree) { + return None; + } + if right.with_tree.intersects(left.without_tree) { + return None; + } + if left.with_tree.intersects(right.without_tree) { + return None; + } + // END-REGION + + right.component_tree.self_intersects(left.component_tree) +} +impl ConstTreeInner { + /// Simply combines with trees, we do this in case later we want to do some + /// error checking, like preventing multiple of the same filter. + pub const fn combine(lhs: ConstTree, rhs: ConstTree) -> Self { + Self::Node(lhs, rhs) + } +} +impl ConstTreeInner { + /// Simply combines with trees, we do this in case later we want to do some + /// error checking, like preventing multiple of the same filter. + pub const fn combine(lhs: ConstTree, rhs: ConstTree) -> Self { + Self::Node(lhs, rhs) + } + const fn intersects(&'static self, without_tree: ConstTree) -> bool { + match without_tree { + ConstTreeInner::Empty => false, + ConstTreeInner::Leaf(without_id) => match self { + ConstTreeInner::Empty => false, + ConstTreeInner::Leaf(with_id) => with_id.0 == without_id.0, + ConstTreeInner::Node(component_tree_lhs, component_tree_rhs) => { + component_tree_lhs.intersects(without_tree) + || component_tree_rhs.intersects(without_tree) + } + ConstTreeInner::PanicMessage(_) => unreachable!(), + }, + ConstTreeInner::Node(without_lhs, without_rhs) => { + self.intersects(without_lhs) || self.intersects(without_rhs) + } + ConstTreeInner::PanicMessage(_) => unreachable!(), + } + } +} + +impl ConstTreeInner { + /// Combines together two Component Access trees and looks for conflicts + pub const fn combine(lhs: ConstTree, rhs: ConstTree) -> Self { + if let Some(panic_message) = lhs.self_intersects(rhs) { + return Self::PanicMessage(panic_message); + } + Self::Node(lhs, rhs) + } + const fn intersects(&'static self, without_tree: ConstTree) -> bool { + match without_tree { + ConstTreeInner::Empty => false, + ConstTreeInner::Leaf(without_id) => match self { + ConstTreeInner::Empty => false, + ConstTreeInner::Leaf(component_access) => component_access.type_id == without_id.0, + ConstTreeInner::Node(component_tree_lhs, component_tree_rhs) => { + component_tree_lhs.intersects(without_tree) + || component_tree_rhs.intersects(without_tree) + } + ConstTreeInner::PanicMessage(_) => unreachable!(), + }, + ConstTreeInner::Node(without_lhs, without_rhs) => { + self.intersects(without_lhs) || self.intersects(without_rhs) + } + ConstTreeInner::PanicMessage(_) => unreachable!(), + } + } + const fn self_intersects( + &'static self, + right: ConstTree, + ) -> Option { + match self { + ConstTreeInner::Empty => { + if let ConstTreeInner::PanicMessage(panic_message) = right { + Some(*panic_message) + } else { + None + } + } + ConstTreeInner::Leaf(component_access) => match right { + ConstTreeInner::Empty => None, + ConstTreeInner::Leaf(component_access_2) => { + component_access.conflicts(component_access_2) + } + ConstTreeInner::Node(rhs, lhs) => { + if let Some(panic_message) = self.self_intersects(rhs) { + Some(panic_message) + } else { + self.self_intersects(lhs) + } + } + ConstTreeInner::PanicMessage(panic_message) => Some(*panic_message), + }, + ConstTreeInner::Node(lhs, rhs) => { + if let Some(panic_message) = right.self_intersects(rhs) { + Some(panic_message) + } else { + right.self_intersects(lhs) + } + } + ConstTreeInner::PanicMessage(panic_message) => Some(*panic_message), + } + } +} + +/// A trait for providing a const ComponentAccessTree on Components without adding them directly +/// to the Component type +pub(crate) trait AccessTreeContainer { + const COMPONENT_ACCESS_TREE: ConstTree; +} + +impl AccessTreeContainer for &T { + const COMPONENT_ACCESS_TREE: ConstTree = + &ConstTreeInner::Leaf(ComponentAccess { + type_id: T::UNSTABLE_TYPE_ID, + access: AccessType::Ref, + #[cfg(feature = "diagnostic_component_names")] + name: T::STRUCT_NAME, + #[cfg(not(feature = "diagnostic_component_names"))] + name: Some(""), + }); +} + +impl AccessTreeContainer for &mut T { + const COMPONENT_ACCESS_TREE: ConstTree = + &ConstTreeInner::Leaf(ComponentAccess { + type_id: T::UNSTABLE_TYPE_ID, + access: AccessType::Mut, + #[cfg(feature = "diagnostic_component_names")] + name: T::STRUCT_NAME, + #[cfg(not(feature = "diagnostic_component_names"))] + name: Some(""), + }); +} + +/// A trait for validating system parameter combinations at compile time. +/// Implementing types provide a const evaluation mechanism to detect invalid +/// parameter combinations before runtime. +pub trait ValidSystemParams { + /// Compile-time error detection for system parameters + /// Contains validation results from checking parameter compatibility + const SYSTEM_PARAMS_COMPILE_ERROR: Option; +} +use crate::system::SystemParamItem; +macro_rules! impl_valid_system_params_for_fn { + ($($param:ident),+) => { + impl ValidSystemParams<($($param,)*)> + for Func + where + Func: Send + Sync + 'static, + for<'a> &'a mut Func: FnMut($($param),*) -> Out + FnMut($(SystemParamItem<$param>),*) -> Out, + { + const SYSTEM_PARAMS_COMPILE_ERROR: Option = const { + #[allow(unused_mut)] + let mut system_panic_message = None; + impl_valid_system_params_for_fn!(@check_all system_panic_message, $($param),+); + system_panic_message + }; + } + }; + + (@check_all $system_panic_message:ident, $t0:ident) => { + if let ConstTreeInner::PanicMessage(system_panic_msg) = $t0::COMPONENT_ACCESS_TREE { + $system_panic_message = Some(*system_panic_msg); + } + }; + + (@check_all $system_panic_message:ident, $t0:ident, $($rest:ident),+) => { + // Check t0 against all remaining elements + + $( + if let Some(system_panic_msg) = check_system_parameters_for_conflicts( + $t0::CONST_TREES, + $rest::CONST_TREES, + ) { + $system_panic_message = Some(system_panic_msg); + } + )* + // Recursively check remaining elements + impl_valid_system_params_for_fn!(@check_all $system_panic_message, $($rest),+); + }; +} + +all_tuples!(impl_valid_system_params_for_fn, 1, 16, T); +impl ValidSystemParams<()> for Func +where + Func: Send + Sync + 'static, + for<'a> &'a mut Func: FnMut() -> Out + FnMut() -> Out, +{ + const SYSTEM_PARAMS_COMPILE_ERROR: Option = None; +} + + +pub mod constime { + use variadics_please::all_tuples; + use bevy_ecs::prelude::Component; + use bevy_ecs::query::QueryFilter; + + #[derive(Copy, Clone, Debug)] + pub struct ComponentInfo { + /// Unique compile-time identifier for the component type + pub type_id: u128, + /// The component's name if available, used for error reporting + pub name: &'static str, + } + + pub enum FilteredAccess { + Empty, + Leaf(&'static FilteredAccess), + Node(&'static FilteredAccess, &'static FilteredAccess), + ComponentReadAndWrites(ComponentInfo), + ComponentWrites(ComponentInfo), + ResourceReadsAndWrites(ComponentInfo), + ResourceWriter(ComponentInfo), + ComponentReadAndWritesInverted, + ComponentWritesInverted, + ReadsAllResources, + WritesAllResources, + With(ComponentInfo), + Without(ComponentInfo), + } + + pub trait FilteredAccessHolder { + const FILTERED_ACCESS: &'static FilteredAccess; + } + impl FilteredAccessHolder for bevy_ecs::query::With { + const FILTERED_ACCESS: &'static FilteredAccess = &FilteredAccess::With(ComponentInfo { + type_id: T::UNSTABLE_TYPE_ID, + name: T::STRUCT_NAME, + }); + } + + impl FilteredAccessHolder for bevy_ecs::query::Without { + const FILTERED_ACCESS: &'static FilteredAccess = &FilteredAccess::Without(ComponentInfo { + type_id: T::UNSTABLE_TYPE_ID, + name: T::STRUCT_NAME, + }); + } + + impl<'__w, T: Component> FilteredAccessHolder for &'__w mut T { + const FILTERED_ACCESS: &'static FilteredAccess = &FilteredAccess::Node( + &FilteredAccess::ComponentReadAndWrites(ComponentInfo { + type_id: T::UNSTABLE_TYPE_ID, + name: T::STRUCT_NAME, + }), + &FilteredAccess::ComponentWrites(ComponentInfo { + type_id: T::UNSTABLE_TYPE_ID, + name: T::STRUCT_NAME, + }) + ); + } + + impl FilteredAccessHolder for &T { + const FILTERED_ACCESS: &'static FilteredAccess = &FilteredAccess::ComponentReadAndWrites(ComponentInfo { + type_id: T::UNSTABLE_TYPE_ID, + name: T::STRUCT_NAME, + }); + } + macro_rules! impl_or_query_filter { + ($(#[$meta:meta])* $($filter: ident),*) => { + $(#[$meta])* + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + non_snake_case, + reason = "The names of some variables are provided by the macro's caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] + impl<$($filter: QueryFilter,)*> FilteredAccessHolder for bevy_ecs::prelude::Or<($($filter,)*)> { + const FILTERED_ACCESS: &'static FilteredAccess = impl_or_query_filter!(@tree $($filter,)*); + } + }; + + // Base case + (@tree) => { + &FilteredAccess::Empty + }; + // Inductive case + (@tree $t0:ident, $($rest:ident,)*) => { + &FilteredAccess::Node( + $t0::FILTERED_ACCESS, + bevy_ecs::prelude::Or::<($($rest,)*)>::FILTERED_ACCESS, + ) + }; + } + all_tuples!(impl_or_query_filter, 0, 15, T); +} diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 0f3950d1d4ba6..54ed2815f02a9 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -749,6 +749,8 @@ where Marker: 'static, F: SystemParamFunction, { + const INTO_SYSTEM_PANIC_CHECKER: Option = + F::SYSTEM_PARAM_FUNCTION_PANIC_CHECKER; type System = FunctionSystem; fn into_system(func: Self) -> Self::System { FunctionSystem { @@ -990,6 +992,10 @@ where label = "invalid system" )] pub trait SystemParamFunction: Send + Sync + 'static { + /// Compile-time error checker for system parameter functions + /// Contains validation results from checking parameter compatibility + const SYSTEM_PARAM_FUNCTION_PANIC_CHECKER: Option = None; + /// The input type of this system. See [`System::In`]. type In: SystemInput; /// The return type of this system. See [`System::Out`]. @@ -1009,7 +1015,7 @@ pub trait SystemParamFunction: Send + Sync + 'static { /// A marker type used to distinguish function systems with and without input. #[doc(hidden)] pub struct HasSystemInput; - +use crate::system::const_param_checking::{SystemPanicMessage, ValidSystemParams}; macro_rules! impl_system_function { ($($param: ident),*) => { #[expect( @@ -1022,12 +1028,15 @@ macro_rules! impl_system_function { )] impl SystemParamFunction Out> for Func where - Func: Send + Sync + 'static, + Func: Send + Sync + 'static + ValidSystemParams<($($param,)*)>, for <'a> &'a mut Func: FnMut($($param),*) -> Out + FnMut($(SystemParamItem<$param>),*) -> Out, - Out: 'static + Out: 'static, + { + const SYSTEM_PARAM_FUNCTION_PANIC_CHECKER: Option = Func::SYSTEM_PARAMS_COMPILE_ERROR; + type In = (); type Out = Out; type Param = ($($param,)*); @@ -1043,6 +1052,7 @@ macro_rules! impl_system_function { f($($param,)*) } let ($($param,)*) = param_value; + //let _ = Func::SYSTEM_PARAMS_COMPILE_ERROR; call_inner(self, $($param),*) } } diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 4fe489d253d1e..ad98c51b1f962 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -123,6 +123,9 @@ mod adapter_system; mod builder; mod combinator; mod commands; +/// Module containing types and traits for compile-time parameter checking of systems +/// Enables validation of component access patterns and system parameter compatibility +pub mod const_param_checking; mod exclusive_function_system; mod exclusive_system_param; mod function_system; @@ -137,6 +140,8 @@ mod system_registry; use core::any::TypeId; +use crate::system::const_param_checking::SystemPanicMessage; +use crate::world::World; pub use adapter_system::*; pub use builder::*; pub use combinator::*; @@ -153,8 +158,6 @@ pub use system_name::*; pub use system_param::*; pub use system_registry::*; -use crate::world::World; - /// Conversion trait to turn something into a [`System`]. /// /// Use this to get a system from a function. Also note that every system implements this trait as @@ -183,6 +186,10 @@ use crate::world::World; label = "invalid system" )] pub trait IntoSystem: Sized { + /// Compile-time error checker for systems + /// Contains validation results from checking parameter compatibility + const INTO_SYSTEM_PANIC_CHECKER: Option = None; + /// The type of [`System`] that this instance converts into. type System: System; @@ -555,29 +562,29 @@ mod tests { run_system(&mut world, sys); } - #[test] + /*#[test] #[should_panic = "&bevy_ecs::system::tests::A conflicts with a previous access in this query."] fn any_of_with_mut_and_ref() { fn sys(_: Query>) {} let mut world = World::default(); run_system(&mut world, sys); - } + }*/ - #[test] + /*#[test] #[should_panic = "&mut bevy_ecs::system::tests::A conflicts with a previous access in this query."] fn any_of_with_ref_and_mut() { fn sys(_: Query>) {} let mut world = World::default(); run_system(&mut world, sys); - } + }*/ - #[test] + /*#[test] #[should_panic = "&bevy_ecs::system::tests::A conflicts with a previous access in this query."] fn any_of_with_mut_and_option() { fn sys(_: Query)>>) {} let mut world = World::default(); run_system(&mut world, sys); - } + }*/ #[test] fn any_of_with_entity_and_mut() { @@ -601,13 +608,13 @@ mod tests { run_system(&mut world, sys); } - #[test] + /*#[test] #[should_panic = "&mut bevy_ecs::system::tests::A conflicts with a previous access in this query."] fn any_of_with_conflicting() { fn sys(_: Query>) {} let mut world = World::default(); run_system(&mut world, sys); - } + }*/ #[test] fn any_of_has_filter_with_when_both_have_it() { @@ -630,13 +637,13 @@ mod tests { run_system(&mut world, sys); } - #[test] + /*#[test] #[should_panic = "error[B0001]"] fn or_has_no_filter_with() { fn sys(_: Query<&mut B, Or<(With, With)>>, _: Query<&mut B, Without>) {} let mut world = World::default(); run_system(&mut world, sys); - } + }*/ #[test] fn or_has_filter_with_when_both_have_it() { @@ -674,7 +681,7 @@ mod tests { run_system(&mut world, sys); } - #[test] + /*#[test] #[should_panic = "error[B0001]"] fn or_expanded_nested_with_and_disjoint_without() { fn sys( @@ -684,9 +691,9 @@ mod tests { } let mut world = World::default(); run_system(&mut world, sys); - } + }*/ - #[test] + /*#[test] #[should_panic = "error[B0001]"] fn or_expanded_nested_or_with_and_disjoint_without() { fn sys( @@ -696,7 +703,7 @@ mod tests { } let mut world = World::default(); run_system(&mut world, sys); - } + }*/ #[test] fn or_expanded_nested_with_and_common_nested_without() { @@ -720,15 +727,15 @@ mod tests { run_system(&mut world, sys); } - #[test] + /*#[test] #[should_panic = "error[B0001]"] fn with_and_disjoint_or_empty_without() { fn sys(_: Query<&mut B, With>, _: Query<&mut B, Or<((), Without)>>) {} let mut world = World::default(); run_system(&mut world, sys); - } + }*/ - #[test] + /*#[test] #[should_panic = "error[B0001]"] fn or_expanded_with_and_disjoint_nested_without() { fn sys( @@ -738,9 +745,9 @@ mod tests { } let mut world = World::default(); run_system(&mut world, sys); - } + }*/ - #[test] + /*#[test] #[should_panic = "error[B0001]"] fn or_expanded_nested_with_and_disjoint_nested_without() { fn sys( @@ -750,7 +757,7 @@ mod tests { } let mut world = World::default(); run_system(&mut world, sys); - } + }*/ #[test] fn or_doesnt_remove_unrelated_filter_with() { @@ -759,14 +766,14 @@ mod tests { run_system(&mut world, sys); } - #[test] + /*#[test] #[should_panic] fn conflicting_query_mut_system() { fn sys(_q1: Query<&mut A>, _q2: Query<&mut A>) {} let mut world = World::default(); run_system(&mut world, sys); - } + }*/ #[test] fn disjoint_query_mut_system() { @@ -784,23 +791,23 @@ mod tests { run_system(&mut world, sys); } - #[test] + /*#[test] #[should_panic] fn conflicting_query_immut_system() { fn sys(_q1: Query<&A>, _q2: Query<&mut A>) {} let mut world = World::default(); run_system(&mut world, sys); - } + }*/ - #[test] + /*#[test] #[should_panic] fn changed_trackers_or_conflict() { fn sys(_: Query<&mut A>, _: Query<(), Or<(Changed,)>>) {} let mut world = World::default(); run_system(&mut world, sys); - } + }*/ #[test] fn query_set_system() { diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index a12323e26417a..a435d05ca02ac 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -31,6 +31,9 @@ use core::{ use disqualified::ShortName; use super::Populated; +use crate::system::const_param_checking::{ + ComponentAccess, ConstTree, ConstTreeInner, ConstTrees, WithId, WithoutId, +}; use variadics_please::{all_tuples, all_tuples_enumerated}; /// A parameter that can be used in a [`System`](super::System). @@ -192,6 +195,25 @@ pub unsafe trait SystemParam: Sized { /// You could think of [`SystemParam::Item<'w, 's>`] as being an *operation* that changes the lifetimes bound to `Self`. type Item<'world, 'state>: SystemParam; + /// A compile-time representation of how this system parameter accesses components + /// Used for validating access patterns during const evaluation + const COMPONENT_ACCESS_TREE: ConstTree = &ConstTreeInner::Empty; + + /// A compile-time representation of With, Added, and Changed filters for this parameter + /// Used for validating filter compatibility during const evaluation + const WITH_FILTER_TREE: ConstTree = &ConstTreeInner::Empty; + + /// A compile-time representation of Without filters for this parameter + /// Used for validating filter compatibility during const evaluation + const WITHOUT_FILTER_TREE: ConstTree = &ConstTreeInner::Empty; + + /// A collection of the ConstTrees in the SystemParam + const CONST_TREES: ConstTrees = ConstTrees { + component_tree: Self::COMPONENT_ACCESS_TREE, + without_tree: Self::WITHOUT_FILTER_TREE, + with_tree: Self::WITH_FILTER_TREE, + }; + /// Registers any [`World`] access used by this [`SystemParam`] /// and creates a new instance of this param's [`State`](SystemParam::State). fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; @@ -309,6 +331,12 @@ unsafe impl SystemParam for Qu type State = QueryState; type Item<'w, 's> = Query<'w, 's, D, F>; + const COMPONENT_ACCESS_TREE: ConstTree = D::COMPONENT_ACCESS_TREE_QUERY_DATA; + + const WITH_FILTER_TREE: ConstTree = F::WITH_FILTER_TREE_QUERY_DATA; + + const WITHOUT_FILTER_TREE: ConstTree = F::WITHOUT_FILTER_TREE_QUERY_DATA; + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { let state = QueryState::new_with_access(world, &mut system_meta.archetype_component_access); init_query_param(world, system_meta, &state);